目录
1.6 SetConsoleCursorPosition函数
1.Win32 API介绍
本次实现贪吃蛇会使⽤到的⼀些Win32 API知识,接下来我们就学习⼀下。
Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤
的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启
视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application), 所以便
称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows
32位平台的应⽤程序编程接⼝。也就是说我们可以调用这些接口来实现我们想要的功能。
1.1控制台程序
平常我们运⾏起来的⿊框程序其实就是控制台程序
我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
mode con cols=
100
lines=
30
也可以通过命令设置控制台窗⼝的名字:
title
贪吃蛇
那么在c语言我们怎么通过程序来实现对控制台的操作呢?
这些能在控制台窗⼝执⾏的命令,也可以调⽤C语⾔函数system来执⾏。
举例:
#
include
<stdio.h>
int
main
()
{
//
设置控制台窗⼝的⻓
宽:设置控制台窗⼝的⼤⼩,
30
⾏,
100
列
system(
"mode con cols=100 lines=30"
);
//
设置
cmd
窗⼝名称
system(
"title
贪吃蛇
"
);
return
0
;
}
1.2控制台屏幕上的坐标COORD
coord就是一个结构体它如下定义:
typedef struct
_
COORD
{
SHORT X;
SHORT Y;
} COORD, *PCOORD;
注意这里的坐标与我们数学的坐标不同
我们应该知道这里的坐标我们应该如何使用就行了。
1.3GetStdHandle函数
首先我们先介绍一下这个函数
GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标
准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。比如我们在这里我们先要操作控制台就要先获得句柄才能进行操作,也就是获得标准输出的句柄
下面是该函数的声明:
参数说明:
返回值说明:
可以看到这里返回值的类型就是一个void*的指针
例子:
HANDLE hOutput =
NULL
;
//
获取标准输出的句柄
(
⽤来标识不同设备的数值
)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
1.4GetConsoleCursorInfo函数
函数介绍:
检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息
就是把光标的信息得到
函数声明:
参数介绍:
我们的第一个参数就是我们获得的句柄,第二个参数是一个结构体指针如上所示其中来存储光标的信息其中第一个成员变量就是存储光标的
由光标填充的字符单元格的百分⽐。 此值介于1到100之间。第二个成员变量就是游标的可⻅性。 如果光标可⻅,则此成员为 TRUE反之则为FALSE.
1.5SetConsoleCursorInfo 函数
函数介绍:
1.6 SetConsoleCursorPosition函数
设置指定控制台屏幕缓冲区中的光
标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调
⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。
函数声明:
函数实例:
1.7GetAsyncKeyState函数
函数介绍:
获取按键情况
函数原型:
返回值:
因此我们可以定义一个宏来检查按键是否被按过
#
define
keycheck(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
其中的参数就是虚拟键值
上面可以自行查看
2.贪吃蛇的实现
2.1本地化
2.1.1setlocale函数
1
char
*
setlocale
(
int
category,
const char
* locale);
setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:
"C"
(正常模式)和
" "
(本地模式)。
在任意程序执⾏开始,都会隐藏式执⾏调⽤:
setlocale
(LC_ALL,
"C"
);
当地区设置为"C"时,库函数按正
常⽅式执⾏,⼩数点是⼀个点。
当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale
函数就可以切换到本地模式,这种模式下程序会适应本地环境。
⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。
2.1.2 宽字符的打印
那如果想在屏幕上打印宽字符,怎么打印呢?
宽字符的字⾯量必须加上前缀“L”,否则 C 语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引
号前⾯,表⽰宽字符,对应
wprintf()
的占位符为
%lc
;在双引号前⾯,表⽰宽字符串,对应
wprintf()
的占位符为
%ls
。
3.游戏实现
首先我们先要绘制地图
这里我们规定设置个棋盘27⾏,58列的棋盘当做墙
4.游戏主逻辑
•
游戏开始GameBegin完成游戏的初始化
•
游戏运⾏Gamerun完成游戏运⾏逻辑的实现
•
游戏结束Gameend完成游戏结束的说明,实现资源释放
5.GameBegin
首先我们建立三个文件一个test.c一个Snake.c一个Snake.h
首先在头文件中我们需要定义一个蛇的节点
紧接着我们要定义一个结构体通过这个结构体来维护整条蛇的信息比如方向,速度,状态等
下面我们开始来实现GameBegin函数
首先我们要把蛇初始化就先要创建一条蛇如下所示:
因为我们要把蛇初始化因此要改变蛇的内容因此我们应该传入蛇的地址这样我们该函数就可以如下声明:
然后取地址传入该函数
进入该函数首先进行对控制台的操作
接下来打印初始界面
接下来进行初始化对蛇的内容进行操作
下面创建打印食物:
6.Gamerun
首先打印帮助信息
接下来进行按键检测并进行移动
其中的keycheck就是我们上面定义的宏按键之后要进行一步移动然后休眠然后接着循环直到蛇的状态被该改变
如果一直这样那么蛇就会一直走因此我们要判断蛇的时候是否撞到墙或者撞到自己导致游戏结束
7.Gameend
这样进行一次游戏的逻辑就已经完成了如果想要再次进行我们可以进行询问让后用循环进行控制
这样就完成了这个游戏
下面是完整代码:
Snake.c
#define _CRT_SECURE_NO_WARNINGS
#include "Snake.h"
//改变光标位置
void setpos(int x, int y)
{
HANDLE outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };
SetConsoleCursorPosition(outhandle, pos);
}
void print_wall()
{
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 show()
{
setpos(35, 12);
wprintf(L"欢迎来到贪吃蛇小游戏");
setpos(38, 25);
system("pause");
system("cls");
setpos(15, 12);
wprintf(L"用 ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速");
setpos(35, 13);
wprintf(L"加速将能得到更高的分数。");
setpos(38, 25);
system("pause");
system("cls");
//打印墙体
print_wall();
}
//初始化蛇身
void Inint_snake(pSnake ps)
{
for (int i = start; i <= 32; i+=2)
{
PSnaekNode pcur = (PSnaekNode)malloc(sizeof(SnakeNode));
if (pcur == NULL)
{
perror("pcur malloc");
return;
}
pcur->next = NULL;
pcur->x = i;
pcur->y = 5;
if (ps->_Snake == NULL)
{
ps->_Snake = pcur;
}
else
{
pcur->next = ps->_Snake;
ps->_Snake = pcur;
}
pcur = ps->_Snake;
while (pcur)
{
setpos(pcur->x, pcur->y);
wprintf(L"%lc", snakebody);
pcur = pcur->next;
}
ps->_food_weight = 10;
ps->_SnakeDeriction = RIGHT;
ps->_Statue = OK;
ps->_sleeptime = 200;//毫秒
ps->_score = 0;
}
}
void Createfood(pSnake ps)
{
//记录食物坐标
int x = 0;
int y = 0;
again:
do
{
x = (rand() % 53) + 2;
y = (rand() % 25) + 1;
} while (x % 2 != 0);//保证食物的坐标可以被蛇给吃掉
//判断食物是否与蛇身重合
PSnaekNode cur = ps->_Snake;
while (cur)
{
if (cur->x == x && cur->y ==y)
{
goto again;
}
cur = cur->next;
}
//申请一个食物节点
ps->_pfood =(PSnaekNode)malloc(sizeof(SnakeNode));
if (ps->_pfood == NULL)
{
perror("pfood");
return;
}
ps->_pfood->x = x;
ps->_pfood->y = y;
ps->_pfood->next = NULL;
setpos(x,y);
wprintf(L"%lc", food);
}
void GameBegin(pSnake ps)
{
//设置窗口大小
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//隐藏光标
HANDLE outhandle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO console_cursor_info;
GetConsoleCursorInfo(outhandle, &console_cursor_info);
console_cursor_info.bVisible = false;
SetConsoleCursorInfo(outhandle, &console_cursor_info);
//打印初始界面
show();
//初始化蛇
Inint_snake(ps);
//创建打印食物
Createfood(ps);
}
//打印帮助信息
void print_helpinfo()
{
setpos(64, 15);
wprintf(L"不能穿墙,不能咬到自己");
setpos(64, 16);
wprintf(L"用↑.↓.←.→分别控制蛇的移动.");
setpos(64, 17);
wprintf(L"F3 为加速,F4 为减速");
setpos(64, 18);
wprintf(L"ESC :退出游戏.space:暂停游戏.");
setpos(64, 19);
wprintf(L"小曹制作");
}
void gamepause()
{
while (1)
{
Sleep(1);
if (keycheck(VK_SPACE))
{
break;
}
}
}
//如果是吃食物并释放掉下一个节点
void EatFood(PSnaekNode pnext, pSnake ps)
{
ps->_pfood->next = ps->_Snake;
ps->_Snake = ps->_pfood;
free(pnext);
pnext = NULL;
//再次打印蛇身
PSnaekNode cur = ps->_Snake;
while (cur)
{
setpos(cur->x, cur->y);
wprintf(L"%lc", snakebody);
cur = cur->next;
}
ps->_score += ps->_food_weight;
//下面在创建一个食物
Createfood(ps);
}
//如果不是先把下一个节点挂上在释放掉最后一个节点
void NoFood(PSnaekNode pnext, pSnake ps)
{
pnext->next = ps->_Snake;
ps->_Snake = pnext;
PSnaekNode pcur = ps->_Snake;
PSnaekNode prev = ps->_Snake;
while (pcur->next)
{
setpos(pcur->x, pcur->y);
wprintf(L"%lc", snakebody);
prev = pcur;
pcur = pcur->next;
}
setpos(pcur->x, pcur->y);
printf(" ");
free(pcur);
prev->next = NULL;
}
//判断下一个节点是不是食物
int check_pnext(PSnaekNode pnext, pSnake ps)
{
return (pnext->x == ps->_pfood->x)&& (pnext->y == ps->_pfood->y);
}
//蛇的移动
void snakemove(pSnake ps)
{
PSnaekNode pnext = (PSnaekNode)malloc(sizeof(SnakeNode));
if (pnext == NULL)
{
perror("pnext");
return;
}
switch (ps->_SnakeDeriction)
{
case UP:
pnext->x = ps->_Snake->x;
pnext->y = ps->_Snake->y - 1;
break;
case DOWN:
pnext->x = ps->_Snake->x;
pnext->y = ps->_Snake->y + 1;
break;
case LEFT:
pnext->x = ps->_Snake->x-2;
pnext->y = ps->_Snake->y;
break;
case RIGHT:
pnext->x = ps->_Snake->x + 2;
pnext->y = ps->_Snake->y;
break;
}
//这里判断下一个节点是不是食物的位置
if (check_pnext(pnext, ps))
{
//如果是吃食物并释放掉下一个节点
EatFood(pnext, ps);
pnext = NULL;
}
else
{
//如果不是先把下一个节点挂上在释放掉最后一个节点
NoFood(pnext, ps);
}
}
//检查是否撞到墙体和自身
void killbywall(pSnake ps)
{
if (ps->_Snake->x == 56 || ps->_Snake->x == 0 || ps->_Snake->y == 0 ||
ps->_Snake->y == 26)
{
ps->_Statue = KILL_BY_WALL;
}
}
void killbyself(pSnake ps)
{
PSnaekNode pcur = ps->_Snake->next;
while (pcur)
{
if (pcur->x == ps->_Snake->x && pcur->y == ps->_Snake->y)
{
ps->_Statue = KILL_BY_SEIF;
break;
}
pcur = pcur->next;
}
}
//进行按键检测
void Check_Key(pSnake ps)
{
do
{
setpos(64, 10);
printf("食物分数:%2d", ps->_food_weight);
setpos(64, 11);
printf("总得分:%d", ps->_score);
if (keycheck(VK_UP)&&ps->_SnakeDeriction!=DOWN)
{
ps->_SnakeDeriction = UP;
}
else if (keycheck(VK_DOWN)&&ps->_SnakeDeriction!=UP)
{
ps->_SnakeDeriction = DOWN;
}
else if (keycheck(VK_LEFT)&&ps->_SnakeDeriction!=RIGHT)
{
ps->_SnakeDeriction = LEFT;
}
else if (keycheck(VK_RIGHT)&&ps->_SnakeDeriction!=LEFT)
{
ps->_SnakeDeriction = RIGHT;
}
else if (keycheck(VK_SPACE))
{
//暂停
gamepause();
}
else if (keycheck(VK_ESCAPE))
{
//退出
ps->_Statue = END_NORMAL;
}
else if (keycheck(VK_F3))
{
//加速
if (ps->_sleeptime > 80)
{
ps->_sleeptime -= 30;
ps->_food_weight += 2;
}
}
else if (keycheck(VK_F4))
{
//减速
if (ps->_food_weight > 2)
{
ps->_sleeptime += 30;
ps->_food_weight -= 2;
}
}
snakemove(ps);
Sleep(ps->_sleeptime);
killbywall(ps);
killbyself(ps);
} while (ps->_Statue == OK);
}
//4.结束游戏
void Gameend(pSnake ps)
{
setpos(24, 13);
switch (ps->_Statue)
{
case END_NORMAL:
printf("您主动结束游戏");
break;
case KILL_BY_SEIF:
printf("您撞到自己游戏结束");
break;
case KILL_BY_WALL:
printf("您撞到墙游戏结束");
break;
}
//进行链表的释放
PSnaekNode pcur = ps->_Snake;
while (pcur)
{
PSnaekNode pnext = pcur->next;
free(pcur);
pcur = pnext;
}
}
//3.运行游戏
void Gamerun(pSnake ps)
{
//打印帮助信息
print_helpinfo();
//进行按键检测并进行移动
Check_Key(ps);
}
Snake.h
#pragma once
#include <locale.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#define start 24
#define wall L'□'
#define snakebody L'●'
#define food L'▲'
#define keycheck(x) (((GetAsyncKeyState(x))&1) ? 1:0)
//创建贪吃蛇结点
typedef struct SnakeNode
{
//x,y是结点的坐标
int x;
int y;
struct SnakeNode* next;
}SnakeNode,*PSnaekNode;
//描述贪吃蛇的信息
enum SnakeDeriction
{
UP =1,
DOWN,
LEFT,
RIGHT
};
enum SnakeStatue
{
KILL_BY_WALL,
KILL_BY_SEIF,
OK,
END_NORMAL
};
typedef struct Snake
{
//指向贪吃蛇头的指针
PSnaekNode _Snake;
//蛇的方向
enum SnakeDeriction _SnakeDeriction;
//蛇的状态
enum SnakeStatue _Statue;
//指向食物的指针
PSnaekNode _pfood;
//蛇的速度时间越短速度越快
int _sleeptime;
//一个食物的分数
int _food_weight;
//总得分
int _score;
}Snake,*pSnake;
// 开始游戏
void GameBegin(pSnake ps);
//改变光标位置
void setpos(int x, int y);
//初始化蛇
void Inint_snake(pSnake ps);
//创建食物
void Createfood(pSnake ps);
//3.运行游戏
void Gamerun(pSnake ps);
//打印帮助信息
void print_helpinfo();
//进行按键检测
void Check_Key(pSnake ps);
//蛇的移动
void snakemove(pSnake ps);
//判断下一个节点是不是食物
int check_pnext(PSnaekNode pnext, pSnake ps);
//如果是吃食物并释放掉下一个节点
void EatFood(PSnaekNode pnext, pSnake ps);
//如果不是先把下一个节点挂上在释放掉最后一个节点
void NoFood(PSnaekNode pnext, pSnake ps);
//检查是否撞到墙体和自身
void killbywall(pSnake ps);
void killbyself(pSnake ps);
//4.结束游戏
void Gameend(pSnake ps);
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "Snake.h"
#include <time.h>
void test()
{
char ch = 0;
do
{
system("cls");
//1.创建贪吃蛇
Snake snake = { 0 };
// 2.初始化游戏
GameBegin(&snake);
//3.运行游戏
Gamerun(&snake);
//4.结束游戏
Gameend(&snake);
setpos(23, 11);
printf("再来一局吗?Y/N");
ch = getchar();
while (getchar()!='\n');
} while (ch == 'y' || ch == 'Y');
setpos(0, 26);
}
#include <stdio.h>
int main()
{
//生成随机数
srand((unsigned)time(NULL));
//进行本地化
setlocale(LC_ALL, "");
test();
return 0;
}
到这里贪吃蛇这个游戏我们已经实现了如果各位喜欢点赞加关注啊~