先来看看效果:
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语言语法部分看看可能会补充一部分,还是看看时间吧,大家加油!!!