C语言:【项目实现】贪吃蛇
轩源源 2024-10-03 15:05:13 阅读 64
目录
1.介绍:
(1).控制台程序(Console)(也就是控制控制台窗口的长和宽)
(2).控制光标位置
(3).GetStdHandle函数:
(4).GetConsoleCursorInfo函数:
(5).SetConsoleCursorInfo函数:
(6).SetConsoleCursorPosition函数:
(7).GetAsyncKeyState:
2.宽字符打印与本地化设置:
(1).前情提要:
(2).本地化介绍:本地化:
(3).类项:
(4).setlocale函数:设置本地化状态
(5).宽字符的打印:
3.贪吃蛇游戏的实现:
(1).test.c文件:
(2).snake.h
(3).snake.c
Hello everyone,今天我来教大家实现一下贪吃蛇游戏的相关操作:
首先呢,我们大家在写贪吃蛇代码前要先了解一些知识点(Win32 API技术),在这里,我就带着大家一起来看一下吧:
1.介绍:
Windows这个多作业系统除了协调应用程序的执行等操作,他同时也是一个很大的服务中心,这个服务中心它包含着很多种类的服务,其实这些服务就是有一系列的函数组成的,换句话说,就是我们现在在这个电脑上所完成的一系列步骤,其实在都是由函数来实现的,Windows规定这些函数我们都可以其直接使用,比如说打开视窗,描绘图形,使用周边设备等功能,也就是Windows的每一个函数他都能完成一个特定的功能,因为这些函数服务的对象是应用程序(Applicancation),因此简称API函数,Win32 API也就是Microsoft Windows 32位平台的应用程序编程接口。那么,接下来,我就来给大家介绍几种实现我们的贪吃蛇游戏需要用到的函数吧。
(1).控制台程序(Console)(也就是控制控制台窗口的长和宽)
但我们想要去改变控制台的长和宽的时候,mode命令去实现这个功能,例如:mode con coles=100 lines=30,当我们在Windows系统控制台上输入这个命令时,然后按Enter键那么这个时候控制台就会变成长100,宽30。
我们也可以通过title命令去改变控制台窗口的名字,例如:title 贪吃蛇,当我们在Windows系统控制台上输入这个命令时,然后按Enter键那么这个时候控制台的题目就会变成贪吃蛇(只仅限于是在程序没结束时控制台的题目一直都会使贪吃蛇)。
注意:mode命令和title命令不可以直接在C语言中使用,但是我们可以使用system()函数来执行这一命令,代码如下所示:
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
调试控制台屏幕如图所示:调试控制台屏幕变成100行30列,而且题目变成贪吃蛇。
(2).控制光标位置
在了解这个知识点前,我们首先要知道控制台屏幕其实就是一个坐标轴,而其中的每一个位置就是那个白点一样是一个坐标,
我们要想让光标定位(注意:现在只是定位,不是转变到这个位置)到某一个坐标位置,在Win32 API上面有一个结构体:COORD结构体:typedef struct _COORD{
SHORT x;
SHORT y;
}COORD,*PCOORD;例如:COORD pos={10,30};//给这个结构体x赋值10,y赋值30,换句话说,pos定位到(10,30)这个坐标位置。
(3).GetStdHandle函数:
用于从一个特定的标准设备(标准输入(例如键盘),标准输出(屏幕),标准错误)中获得一个句柄(换句话说,就是你想要去操作一个设备,你就得去获得这个设备的一些数据,(比如说:你现在想要去使用锅,那么你就得抓住这口锅的手柄,这样你才能去操作这口锅)这里的句柄和锅的手柄是一个道理,当我们得到这个句柄之后,我们就可以去操作这个设备了)。
HANDLE GetStdHandle (DWORD nStdHandle);//(HANDLE是句柄的类型,DWORD是GetStdHandle参数的类型,nStdHandle是参数的取值)
{STD_INPUT_HANDLE 标准输入设备(输入缓冲区的控制台)
nStdHandle的取值{STD_OUTPUT_HANDLE 标出输出设备(控制台屏幕缓冲区)
{STD_ERROR_HANDLE 标准错误设备(控制台屏幕缓冲区)
例如:HANDLE output=GetStdHandle(STD_OUTPUT_HANDLE);//定义了一个句柄output里面有标准输出是设备的一些信息。
(4).GetConsoleCursorInfo函数:
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息(获得光标信息)。
BOOL WINAPT GetConsoleCursorInfo(HANDLE nStdHandle,PCONSOLE_CURSOR_INFO IpConsoleCursorInfo);//(nStdHandle句柄,PCONSOLE_CURSOR_INFO是指向CONSOLE_CURSOR_INFO结构体的指针,该结构接收有关主机游标(光标)的信息
typedef struct _CONSOLE_CURSOR_INFO{
DWORD dwSize;//光标的大小
BOOL bVisible;//光标的显示度
};CONSOLE_CURSOR_INFO,*PCONSOLE_CURSOR_INFO;)。
例如:我想要获得控制台屏幕中光标的信息
HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_IN cursor_info={0};
GetConsoleCursorInfo(houtput,&cursor_info);//将houtput句柄中有关光标的大小和显示度这两个信息放到cursor_info数组里面。(注意:这个函数仅仅只是获得信息,并不是改变)
(5).SetConsoleCursorInfo函数:
设置指定控制台屏幕缓的光标大小和可见度。
BOOL WINAPT SetConsoleCursorInfo(HANDLE nStdHandle,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo);//(nStdHandle句柄,CONSOLE_CURSOR_INFO *lpConsoleCursorInfo是指向CONSOLE_CURSOR_INFO结构体的指针)
例如: HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_IN cursor_info={0};
GetConsoleCursorInfo(houtput,&cursor_info);
cursor_info.bVisible=false;
SetConsoleCursorInfo(houtput,&cursor_info);//将cursor_info中的光标信息在控制台屏幕中展现出来。
(6).SetConsoleCursorPosition函数:
设置光标位置。
BOOL WINAPT SetConsoleCursorInfo(HANDLE nStdHandle,COORD dwCurPosition);//(nStdHandle句柄,dwCurPosition是COORD类型的结构体
typedef struct _COORD{
SHORT x;
SHORT y;
}COORD,*PCOORD;)
例如: HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos{10,20};
SetConsoleCursorPosition(houtput,pos);//将屏幕中光标的位置定位到pos中所指定的位置。
(7).GetAsyncKeyState:
获取键盘中各键的状态,它会返回一个16位的short类型的数据,如果返回值的最低位被置为1则说明,该键被按过,否则为0。
SHORT GetAsyncKeyState(int vkey);//(每个键都有一个对应的虚拟键值,再此函数中就是指vkey)
例如:我想要判断一下键5是否被按过:
#define AK(vk) ((GetAsyncKeyState(vk)&0x1)?1:0)//这里我们需要注意一下,GetAsyncKeyState函数它的返回值的16位的值是不确定的,但是我们只需要去判断它的最后是不是1或者是0即可。基于这种原因,我们让GetAsyncKeyState函数的返回值和0x1&,也就是如下列代码所示:比如说GetAsyncKeyState函数返回了值是
(函数返回的随机值)& 00101010 01010101 | & 00101010 01010100
00000000 00000001 | 00000000 00000001
最后的结果为 00000000 00000001 | 00000000 00000000
2.宽字符打印与本地化设置:
(1).前情提要:
(2).本地化介绍:<local.h>本地化:
此头文件中所提供的函数用于控制C语言库中对于不同的地区会产生不一样行为的部分。换句话说,就是这个C语言在最开始的时候它是由美国人创造的,因此它仅限与使用英语地区的人使用,但是当你本地化之后,你就可以在屏幕上打印出汉字(我们的汉字占两个坐标位置(也就是可以打印宽字符))。
(3).类项:
通过上述解释,相信大家对于本地化已经有了一些了解,但是怎么修改呢?
比如:LC_ALL------针对所有类项进行修改
LC_TIME----只影响时间的格式(类项不只有这两个,这里时间有限,就不给大家一一去介绍了,大家感兴趣的话可以去自行了解)
(4).setlocale函数:设置本地化状态
char*setlocale(int category,const char*locale);//(category这个参数指的就是上述类项其中的一种,针对第二个参数,标准只给了两种取值"C"(正常模式(也就是英语模式))和""(本地模式))。
例如: setlocale(LC_ALL,"");//将所有的类项均转换为本地模式。
(5).宽字符的打印:
当我们将其转化为本地模式后,那么此时的编译器按逻辑来说是可以打印出汉字的,但还是要做一些处理,所用到的函数是什么,占位符是什么。
我们在打印宽字符的时候只能使用wprintf函数,若打印的是宽字符,则所需占位符为%Lc,若为宽字符串,则所需占位符为%Ls。
例如: setlocale(LC_ALL,"");
wprintf(L"%Lc",L'比');//比,占两个坐标位置
wprintf(L"%Ls",L'进击的巨人');//进击的巨人
3.贪吃蛇游戏的实现:
OK好的,同志们,经过了上述知识点的解释,现在以我们的知识储备量就可以开始我们的游戏了。
(1).test.c文件:
<code>#include "snake.h"
//完成的是游戏的测试逻辑
void test()
{
int ch = 0;
do
{
system("cls");
Snake snake = { 0 };
stratgame(&snake);
rungame(&snake);
//finishgame(&snake);
setpos(20, 15);
wprintf(L"再来一局吗?(Y/N):");
ch = getchar();
while (getchar() != '\n');
//system("cls");
} while (ch == 'y' || ch == 'Y');
setpos(0, 27);
}
int main()
{
//设置适配本地环境
setlocale(LC_ALL, "");
srand((unsigned int)time(NULL));
test();
return 0;
}
(2).snake.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <locale.h>
#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 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 stratgame(pSnake ps);
//打印环境页面和功能介绍
void welcomesnake();
//创建地图
void createmap();
//创建蛇
void createsnake(pSnake ps);
//创建食物
void createfood(pSnake ps);
//运行游戏
void rungame(pSnake ps);
//打印帮助信息
void printhelpinfo();
//游戏暂停
void pause();
//贪吃蛇移动
void movesnake(pSnake ps);
//吃食物
void eatfood(pSnakeNode newnode, pSnake ps);
//不是食物
void nofood(pSnakeNode newnode, pSnake ps);
//碰到墙壁
void killbywall(pSnake ps);
//碰到自己
void killbyself(pSnake ps);
//结束游戏
void finishgame(pSnake ps);
(3).snake.c
#include "snake.h"
//创建食物
void createfood(pSnake ps)
{
int x = 0;
int y = 0;
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
pSnakeNode cur = ps->_pSnake;
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));
if (food == NULL)
{
perror("createfood()::malloc()");
return 1;
}
food->x = x;
food->y = y;
food->next = NULL;
setpos(x, y);
wprintf(L"%lc", FOOD);
ps->_pFood = food;
}
//创建蛇
void createsnake(pSnake ps)
{
pSnakeNode cur = NULL;
for (int i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("createsnake()::malloc()");
return 1;
}
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->_food_weight = 10;
ps->_score = 0;
ps->_sleep_time = 200;
ps->_status = OK;
}
//创建地图
void createmap()
{
int i = 0;
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);
}
setpos(0, 26);
for (i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
}
//定位光标位置
void setpos(short x, short y)
{
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };
SetConsoleCursorPosition(houtput, pos);
}
//打印环境页面和功能介绍
void welcomesnake()
{
setpos(40,14);
wprintf(L"%ls",L"欢迎来到贪吃蛇小游戏\n");
setpos(42, 14);
system("pause");
system("cls");
setpos(25,14);
wprintf(L"%ls", L"用↑.↓.←.→ 来控制蛇的移动,按F3加速,F4减速\n");
setpos(25,15);
wprintf(L"%ls", L"加速能得到更好的分数\n");
setpos(42, 20);
system("pause");
system("cls");
}
//游戏开始
void stratgame(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);
//打印环境页面和功能介绍
welcomesnake();
//创建地图
createmap();
//创建蛇
createsnake(ps);
//创建食物
createfood(ps);
}
//判断按键是否被按
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//游戏暂停
void pause()
{
while (1)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//碰到自己
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 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 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 nofood(pSnakeNode newnode, pSnake ps)
{
newnode->next = ps->_pSnake;
ps->_pSnake = newnode;
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 eatfood(pSnakeNode newnode, pSnake ps)
{
ps->_pFood->next = ps->_pSnake;
ps->_pSnake = ps->_pFood;
free(newnode);
newnode=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 movesnake(pSnake ps)
{
pSnakeNode newnode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (newnode == NULL)
{
perror("movesnake()::malloc()");
return 1;
}
switch (ps->_dir)
{
case RIGHT:
newnode->x = ps->_pSnake->x + 2;
newnode->y = ps->_pSnake->y;
break;
case LEFT:
newnode->x = ps->_pSnake->x - 2;
newnode->y = ps->_pSnake->y;
break;
case DOWN:
newnode->x = ps->_pSnake->x;
newnode->y = ps->_pSnake->y + 1;
break;
case UP:
newnode->x = ps->_pSnake->x;
newnode->y = ps->_pSnake->y - 1;
break;
}
if (newnode->x == ps->_pFood->x && newnode->y == ps->_pFood->y)
{
eatfood(newnode,ps);
}
else
{
nofood(newnode, ps);
}
killbywall(ps);
killbyself(ps);
}
//运行游戏
void rungame(pSnake ps)
{
printhelpinfo();
do
{
setpos(64, 10);
printf("总分数:%d\n", ps->_score);
setpos(64, 11);
printf("当前食物的分数:%2d\n", ps->_food_weight);
if (ps->_dir != RIGHT && KEY_PRESS(VK_LEFT))
{
ps->_dir = LEFT;
}
else if (ps->_dir != LEFT && KEY_PRESS(VK_RIGHT))
{
ps->_dir = RIGHT;
}
else if (ps->_dir != UP && KEY_PRESS(VK_DOWN))
{
ps->_dir = DOWN;
}
else if (ps->_dir != DOWN && KEY_PRESS(VK_UP))
{
ps->_dir = UP;
}
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;
}
}
movesnake(ps);
Sleep(ps->_sleep_time);
} while (ps->_status == OK);
}
//结束游戏
void finishgame(pSnake ps)
{
setpos(24, 12);
switch (ps->_status)
{
case KILL_BY_WALL:
wprintf(L"%ls", L"您撞到墙上,结束游戏\n");
break;
case KILL_BY_SELF:
wprintf(L"%ls", L"您撞到自己,结束游戏\n");
break;
case END_NORMAL:
wprintf(L"%ls", L"您主动结束游戏\n");
break;
}
pSnakeNode cur = ps->_pSnake;
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。