先来看看效果:
20240420_212115
文章目录:
- 3.项目实现
- 3.0宽字符的打印
- 3.01本地化操作
- setlocale函数
- 宽字符的打印
- 3.1贪吃蛇结构的创建和维护
- 3.11贪吃蛇结构的创建
- 3.12贪吃蛇的维护
- 3.2初始化游戏
- 3.21.打印欢迎界面、隐藏光标和设置窗口大小
- 3.22.绘制地图
- 3.23.创建蛇
- 3.24.创建食物
- 3.3 游戏运行逻辑
- 3.31打印游戏旁的提示
- 3.32按键检测
- 3.33蛇的移动
- 3.34检测是否撞墙或自己
- 3.4 游戏结束(善后工作)
- 4.项目代码
3.项目实现
3.0宽字符的打印
3.01本地化操作
先要进行本地化,然后才能进行宽字符的打印
<locale.h>提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。
在标准中,依赖地区的部分有以下⼏项:
• 数字量的格式
• 货币量的格式
• 字符集
• ⽇期和时间的表⽰形式
每个类项的详细说明,请参考:https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-170
setlocale函数
setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项
C标准给第⼆个参数仅定义了2种可能取值:“C”(正常模式)和""(本地模式)
我们来看看示例:
上面是C语言默认的环境,下面就是我们适配本地的环境了
宽字符的打印
宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引号前⾯,表⽰宽字符,对应wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应wprintf() 的占位符为%ls
所以在进行坐标判断是要注意是否为2的倍数
3.1贪吃蛇结构的创建和维护
3.11贪吃蛇结构的创建
我们的贪吃蛇使用链表进行维护我们先进行声明
这里将结构体指针重命名为pSnakeNode方便后续书写
3.12贪吃蛇的维护
我们创建一个结构体来方便维护我们的程序(这里有枚举的方法)
然后我们创建贪吃蛇
3.2初始化游戏
初始化游戏我们要做什么:
1.打印欢迎界面、隐藏光标和设置窗口大小
2.绘制地图
3.创建蛇
4.创建食物
3.21.打印欢迎界面、隐藏光标和设置窗口大小
这些上一篇文章已经写过了就不多赘述了
光标定位也是前一篇写过的,直接拿过来使用即可
这里就是定位光标位置然后打印信息
然后就是清屏然后在打印
3.22.绘制地图
这里创建一个27行58列的地图,也可以根据自己的情况来
这里要注意光标的定位,通过循环就可以将地图绘制出来了
3.23.创建蛇
循环申请空间然后进行初始化,这里是从尾巴向头部申请空间的(蛇初始向右移动)
然后用头插法将其串起来
写成代码就是上面的方式
接着就是将蛇的身体打印出来,用循环遍历就行
并且将蛇的属性进行初始的设置
3.24.创建食物
由于食物要放到指针中,所以要传参数
要注意:
x坐标必须是2的倍数
⻝物的坐标不能和蛇⾝每个节点的坐标重复
接下来判断是否为2的倍数
判断x和y不能和蛇的身体冲突
申请食物的空间,并将其打印出来
3.3 游戏运行逻辑
3.31打印游戏旁的提示
3.32按键检测
这里暂停为循环睡眠,然后在输入空格跳出循环
加减速要注意有判断,不能一直减速
3.33蛇的移动
要不断检测按键来移动蛇的位置
由于要改变游戏的状态所以也要传指针
我们的移动采用的方法是:将未接点释放,头节点进行头插
先创建节点,然后将移动的下一个坐标写出来,要注意x一次加2
检测下一个坐标是否是食物
然后是这两个函数的处理:
用头插法将食物节点插入,释放下一个位置的节点,打印蛇,最后创建食物
将下一个节点头插到蛇头,然后将尾接点用空格覆盖掉,最后将尾接点释放掉
3.34检测是否撞墙或自己
检测头的横纵坐标是否为墙
检测头的坐标是否为身体
3.4 游戏结束(善后工作)
打印结束信息:
然后释放节点
接下来就可以将游戏循环起来
这里要注意输入y后按回车getchar会读取\n,如果想将\n清理掉,就要在调用getchar
在更改一下:
4.项目代码
snake.h
#pragma once
#include <locale.h>
#include <stdio.h>
#include <Windows.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
//wchar_t ch1 = L'●';
//wchar_t ch2 = L'★';void SetPos(short x, short y);
//类型的声明
//蛇身的节点类型
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode;
typedef struct SnakeNode* pSnakeNode;enum DIRECTION //记录方向(枚举)
{UP = 1,DOWN,LEFT,RIGHT
};
//蛇的状态(正常,撞墙,撞到自己,正常退出)
enum GAME_STATUS
{OK, //正常KILL_BY_WALL, //撞墙KILL_BY_SELF, //撞到自己END_NORMAL //正常退出
};
typedef struct Snake
{pSnakeNode _pSnake;//指向蛇头变量pSnakeNode _pFood;//指向食物的指针enum DIRECTION _dir;//记录方向(枚举)enum GAME_STATUS _status;//游戏的状态int _food_weight; //食物的分数int _score;//总成绩int _sleep_time;//休息时间(时间越短,速度越快)
}Snake;
typedef Snake* pSnake;void GameStart(pSnake ps);//初始化游戏void WelcomeToGame();//欢迎界面void GreateMap();//绘制地图void InitSnake(pSnake ps);//创建蛇void CreateFood(pSnake ps); //创建食物void GameRun(pSnake ps);//游戏运行逻辑void SnackMove(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 GameEend(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, 13);printf("欢迎来到贪吃蛇小游戏\n");SetPos(42, 20);system("pause");system("cls");SetPos(27, 13);wprintf(L"用↑.↓.←.→分别控制蛇的移动,F3为加速,F4为减速\n");SetPos(27, 14);printf("加速能获得更高的分数\n");SetPos(40, 20);system("pause");system("cls");
}#define WALL L'□'
#define BODY L'●'
void GreateMap()
{//上for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//左for (int i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (int i = 1; i < 26; i++){SetPos(56, i);wprintf(L"%lc", WALL);}}#define POS_X 24
#define POS_Y 5
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;for (int i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("malloc");exit(1);}cur->next = NULL;cur->x = POS_X + i * 2;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;
}#define FOOD L'★'
void CreateFood(pSnake ps)
{int x = 0;int y = 0;
again:do{x = rand() % 53 + 2;y = rand() % 24 + 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 ("pFood::malloc");exit(1);}pFood->x = x;pFood->y = y;pFood->next = NULL;SetPos(x, y);wprintf(L"%lc", FOOD);ps->_pFood = pFood;
}void GameStart(pSnake ps)
{//1.打印欢迎界面、隐藏光标和设置窗口大小//设置窗口大小system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标//获得标准输出设备HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//创建光标信息结构体CONSOLE_CURSOR_INFO cursor_info = { 0 };//获取光标的信息,放在cursor_info中GetConsoleCursorInfo(houtput, &cursor_info);//修改光标占比//cursor_info.dwSize = 100;cursor_info.bVisible = false;//设置光标信息SetConsoleCursorInfo(houtput, &cursor_info);//1.打印欢迎界面WelcomeToGame();//2.绘制地图GreateMap();//3.创建蛇InitSnake(ps);//4.创建食物CreateFood(ps);
}void PrintHelpInfo()
{SetPos(64, 15);wprintf(L"%ls\n", L"不能穿墙,不能咬到自己");SetPos(64, 16);wprintf(L"%ls\n", L"用↑.↓.←.→分别控制蛇的移动.");SetPos(64, 17);wprintf(L"%ls\n", L"F3 为加速,F4为减速");SetPos(64, 18);wprintf(L"%ls\n", 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)
{assert(ps->_pFood);return (pn->x == ps->_pFood->x && pn->y == ps->_pFood->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);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 SnackMove(pSnake ps)
{//创建一个节点来表示蛇即将到的下一个节点pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnackMove()::malloc()");exit(1);}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", ps->_score);SetPos(64, 11);printf("当前食物分数:%2d", 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_RIGHT) && ps->_dir != LEFT){ps->_dir = RIGHT;}else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT){ps->_dir = LEFT;}else if (KEY_PRESS(VK_SPACE)){//暂停Pause();}else if (KEY_PRESS(VK_ESCAPE)){//正常退出ps->_status = END_NORMAL;}else if (KEY_PRESS(VK_F4)){//加速if (ps->_sleep_time > 80){ps->_sleep_time -= 30;ps->_food_weight += 2;}}else if (KEY_PRESS(VK_F3)){//减速if (ps->_food_weight > 2){ps->_sleep_time += 30;ps->_food_weight -= 2;}}SnackMove(ps);//蛇的移动Sleep(ps->_sleep_time);} while (ps->_status == OK);}void GameEend(pSnake ps)
{SetPos(24, 12);switch (ps->_status){case END_NORMAL:printf("您主动结束游戏\n");break;case KILL_BY_WALL:printf("您撞到了墙上,游戏结束\n");break;case KILL_BY_SELF:printf("您撞到了自己,游戏结束\n");break;}//释放节点pSnakeNode cur = ps->_pSnake;pSnakeNode del = ps->_pSnake;while (cur){del = cur;cur = cur->next;free(del);}SetPos(0,27);
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "snake.h"void Test()
{int ch = 0;do{system("cls");//创建贪吃蛇Snake snake = { 0 };//初始化游戏GameStart(&snake);运行游戏GameRun(&snake);结束游戏(善后工作)GameEend(&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;
}
好了本篇文章就结束了,希望大家可以动手自己去写一下,还是会有一些收获的
到这里C语言的语法部分就差不多了,后续我会继续写C语言数据结构的文章了,C语言语法部分看看可能会补充一部分,还是看看时间吧,大家加油!!!