自学内容网 自学内容网

C语言从零实现贪吃蛇小游戏

制作不易,点赞关注一下呗!!!

文章目录

  • 前言
  • 一. 技术要点
  • 二、WIN32API介绍
  • 三、贪吃蛇游戏设计与分析 
  •       1.游戏开始前的初始化
  •       2.游戏运行的逻辑 
  • 总结


前言

当我们掌握链表这样的数据结构之后,我们就可以用它来做一些小项目,比如童年小游戏贪吃蛇

目标:在Windows环境中控制台上使用C语言模拟实现经典小游戏贪吃蛇


一.技术要点

C语言函数,枚举,结构体,动态内存管理,预处理指令,链表,Win32API等。

二.Win32API介绍

1.什么是Win32API

Win32 API是微软的操作系统Windows提供给开发人员的编程接口,它决定了我们开发的Windows应用程序的能力。

使用Win32 API,应用程序可以充分挖掘Windows的32位操作系统的潜力。 Mircrosoft的所有32位平台都支持统一的API,包括函数、结构、消息及接口。使用 Win32 API不但可以开发出在各种平台上都能成功运行的应用程序,而且也可以充分利用每个平台特有的功能和属性。

2.控制台程序(Console)

我们平时运行起来出现的黑框框就是控制台。

我们可以通过mode命令来设置窗口大小。

例如:设置为30行,100列

mode con cols=100 lines=30 

在VS上我们可以借助system函数(头文件<windows.h>)来完成这样的命令 

 

 我们也可以通过title命令来修改控制台窗口的名称

title 贪吃蛇

 

3.控制台屏幕上的坐标 COORD

COORD是Win32API中定义的一个结构体,表示一个字符在屏幕缓冲区上的坐标,坐标为(0,0)的原点位于缓冲区的顶部最左侧单元格

typedef struct _COORD {

SHORT X; // horizontal coordinate

SHORT Y; // vertical coordinate

} COORD;

4.GetStdHandle 

GetStdHandle 是Win32API的一个函数,它用于从特定的标准设备(标准输入,标准输出,标准错误)中获取一个句柄(用于标识不同设备的数值),使用这个句柄可以操作设备。

语法

HANDLE GetStdHandle( DWORD nStdHandle );

 参数有三种取值

STD_INPUT_HANDLE

标准输入的句柄

STD_OUTPUT_HANDLE

标准输出的句柄

STD_ERROR_HANDLE

标准错误的句柄

实例:

 

 

5.GetConsoleCursorInfo

功能:检索有关指定控制台光标的大小和可见性

 BOOL WINAPI GetConsoleCursorInfo(
   HANDLE               hConsoleOutput,
   PCONSOLE_CURSOR_INFO  lpConsoleCursorInfo
);

  PCONSOLE_CURSOR_INFO是指向 CONSOLE_CURSOR_INFO这个结构体的指针,该结构接收主机光标的信息。

 

6.SetConsoleCursorInfo 

功能:设置指定控制台屏幕缓冲区的光标大小和可见性

BOOL WINAPI SetConsoleCursorInfo(
  _In_       HANDLE              hConsoleOutput,
  _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

一般都是配合 GetConsoleCursorInfo函数使用,先用GetConsoleCursorInfo函数获取当前光标信息,再自己手动修改光标信息,最后用SetConsoleCursorInfo 函数设置进去!!!

 

 7.GetAsyncKeyState 

GetAsyncKeyState是一个用来判断函数调用时指定虚拟键的状态,确定用户当前是否按下了键盘上的一个键的函数。

返回类型为short类型,如果返回的16位short类型数据中最高位为1,则表明该键被按下,最高位为0则表示该键抬起,如果最低位为1表示该键被按过,否则为0.

函数名返回值类型备注
GetAsyncKeyStateSHORT用来判断函数调用时指定虚拟键的状态
参数类型说明
vKeyint欲测试的虚拟键的键码

每个键都对应一个虚拟键值,可以自行查找。

8.SetConsoleCursorPosition

有时我们可能不想让字符打印从(0,0)坐标处开始打印,这时就需要用这个函数来设置坐标

使用这个函数需要两个参数:第一个参数类型为HANDLE,第二个参数类型为COORD

实例:

 

三.贪吃蛇游戏设计与分析 

1.游戏开始前的初始化

数据结构的设计:

enum GameStatus
{
OK,
ESC,
KILL_BYWALL,
KILL_BYSELF,
};
enum DIRECTION
{
LEFT,
RIGHT,
UP,
DOWN,
};
//蛇身节点
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

//整条蛇
typedef struct Snake
{
pSnakeNode pSnake;
pSnakeNode pFood;//指向食物的指针
int Score;//当前累计分数
int FoodWeight;//一个食物的分数
int SleepTime;//休眠时间,时间越短,速度越快
enum GameStatus Status;
enum DIRECTION dir;
}Snake,*pSnake;

 

 

接下来打印欢迎信息 ,这里我们单独分装两个函数,一个用来设置光标位置,另一个用来打印欢迎信息:

 

下一步:创建地图

 #define WALL L'□'
void CreateMap()
{
//上
SetPos(0, 0);
int i = 0;
for (i = 0; i < 69; i += 2)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0,34);
for (i = 0; i < 69; i += 2)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 0; i < 34; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 0; i < 34; i++)
{
SetPos(68, i);
wprintf(L"%lc", WALL);
}
}

 

再下一步:初始化蛇

void InitSnake(pSnake ps)
{
pSnakeNode cur = NULL;
//创建5个蛇身节点
for (int i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("createsnake malloc");
exit(1);
}
cur->x = 32 + 2 * i;
cur->y = 7;
cur->next = NULL;
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->FoodWeight = 10;
ps->pFood = NULL;
ps->Score = 0;
ps->SleepTime = 200;//200毫秒
ps->Status = OK;
}

最后:创建食物 

void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
do {
x = rand() % 66 + 2;
y = rand() % 33 + 1;
} while (x % 2 != 0);
pSnakeNode cur = ps->pSnake;
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;
}
cur = cur->next;
}
pSnakeNode food = (pSnakeNode)malloc(sizeof(pSnake));
if (!food)
{
perror("createfood malloc ");
exit(1);
}
food->x = x;
food->y = y;
ps->pFood = food;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}

 

最后将这些函数放在GameStart函数中,一起完成游戏开始前的工作 

 

2.游戏运行的逻辑 

第一步:打印帮助信息

 

 第二步:循环检测按键状态

 

 

3.游戏结束的善后 

 


总结

所有代码放在这里:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<locale.h>
#include<string.h>
#include<stdbool.h>
#include<windows.h>
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk) & 0x1)? 1: 0)

enum GameStatus
{
OK,
ESC,
KILL_BYWALL,
KILL_BYSELF,
};
enum DIRECTION
{
LEFT,
RIGHT,
UP,
DOWN,
};
//蛇身节点
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

//整条蛇
typedef struct Snake
{
pSnakeNode pSnake;
pSnakeNode pFood;//指向食物的指针
int Score;//当前累计分数
int FoodWeight;//一个食物的分数
int SleepTime;//休眠时间,时间越短,速度越快
enum GameStatus Status;
enum DIRECTION dir;
}Snake,*pSnake;

void SetPos(int x, int y);

void GameStart(pSnake snake);

void Welcome();

void CreateMap();

void InitSnake(pSnake ps);

void CreateFood(pSnake ps);

void printhelpinfo();

//游戏运行的逻辑
void GameRun(pSnake ps);
void pause();

void SnakeMove(pSnake ps);
int IsFood(pSnake ps, pSnakeNode next);
void EatFood(pSnake ps, pSnakeNode next);
void NotEatFood(pSnake ps,pSnakeNode next);

void KillByWall(pSnake ps);
void KillBySelf(pSnake ps);

void GameEnd(pSnake ps);

#include"snake.h"
void SetPos(int x, int y)
{
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x,y };
SetConsoleCursorPosition(handle, pos);
}
void Welcome()
{
//打印欢迎信息
SetPos(48, 15);
printf("欢迎来到贪吃蛇小游戏\n");
SetPos(50,35);
system("pause");
system("cls");

//打印操作信息
SetPos(35, 20);
printf("用↑. ↓. ←. →来控制蛇的移动,F3是加速,F4是减速\n");
SetPos(48, 22);
printf("加速能获得更多分数\n");
SetPos(50, 35);
system("pause");
system("cls");
}

void CreateMap()
{
//上
SetPos(0, 0);
int i = 0;
for (i = 0; i < 69; i += 2)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0,34);
for (i = 0; i < 69; i += 2)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 0; i < 34; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 0; i < 34; i++)
{
SetPos(68, i);
wprintf(L"%lc", WALL);
}
}

void InitSnake(pSnake ps)
{
pSnakeNode cur = NULL;
//创建5个蛇身节点
for (int i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("createsnake malloc");
exit(1);
}
cur->x = 32 + 2 * i;
cur->y = 7;
cur->next = NULL;
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->FoodWeight = 10;
ps->pFood = NULL;
ps->Score = 0;
ps->SleepTime = 200;//200毫秒
ps->Status = OK;
}

void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
do {
x = rand() % 66 + 2;
y = rand() % 33 + 1;
} while (x % 2 != 0);
pSnakeNode cur = ps->pSnake;
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;
}
cur = cur->next;
}
pSnakeNode food = (pSnakeNode)malloc(sizeof(pSnake));
if (!food)
{
perror("createfood malloc ");
exit(1);
}
food->x = x;
food->y = y;
ps->pFood = food;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}

void GameStart(pSnake ps)
{
//修改控制台信息
system("mode con cols=120 lines=40");
system("title 贪吃蛇");
//隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursorinfo;
GetConsoleCursorInfo(handle, &cursorinfo);
cursorinfo.bVisible = false;
SetConsoleCursorInfo(handle, &cursorinfo);
//打印欢迎信息
Welcome();
//绘制地图
CreateMap();
//初始化蛇
InitSnake(ps);
//创建食物
CreateFood(ps);
 
}

void printhelpinfo()
{
SetPos(80, 18);
printf("不能穿墙,不能咬到自己\n");
SetPos(80, 19);
printf("用↑ . ↓ . ← . →来控制蛇的移动\n");
SetPos(80, 20);
printf("F3是加速,F4是减速\n");
}

void pause()
{
while (1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}


int IsFood(pSnake ps,pSnakeNode next)
{
if (ps->pFood->x == next->x && ps->pFood->y == next->y)
{
return 1;//是食物
}
else
return 0;
} 


void EatFood(pSnake ps, pSnakeNode next)
{
next->next = ps->pSnake;
ps->pSnake = next;
//打印蛇身
pSnakeNode cur = ps->pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->Score += ps->FoodWeight;
//释放旧食物
free(ps->pFood);
ps->pFood = NULL;
//创建新食物
CreateFood(ps);
}


void NotEatFood(pSnake ps, pSnakeNode next)
{
//头插法插入
next->next = ps->pSnake;
ps->pSnake = next;

//释放尾节点
pSnakeNode cur = ps->pSnake;
while (cur->next->next)
{
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 SnakeMove(pSnake ps)
{
pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnext == NULL)
{
perror("snakemove malloc");
exit(1);
}
pnext->next = NULL;
switch (ps->dir)
{
case UP:
pnext->x = ps->pSnake->x;
pnext->y = ps->pSnake->y-1;
break;
case DOWN:
pnext->x = ps->pSnake->x;
pnext->y = ps->pSnake->y + 1;
break; 
case LEFT:
pnext->x = ps->pSnake->x-2;
pnext->y = ps->pSnake->y;
break;
case RIGHT :
pnext->x = ps->pSnake->x+2;
pnext->y = ps->pSnake->y;
break;
}

//下一个坐标是否是食物
if (IsFood(ps, pnext))
{
//是食物 
EatFood(ps, pnext);
}
else
{
//不是食物
NotEatFood(ps,pnext);
}
}


void KillByWall(pSnake ps)
{
if (ps->pSnake->x == 0 || ps->pSnake->x == 68 || ps->pSnake->y == 0 || ps->pSnake->y == 33)
{
ps->Status = KILL_BYWALL;
}

}

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_BYSELF;
return;
}
cur=cur->next;
}
}


//游戏运行的逻辑
void GameRun(pSnake ps)
{
//打印帮助信息
printhelpinfo();

do
{
//打印当前分数
SetPos(80, 13);
printf("总分:%d\n", ps->Score);
SetPos(80, 14);
printf("食物的分值:%02d\n", ps->FoodWeight);
//检测按键
//上下左右,空格,F3,F4, ESC
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_ESCAPE))
{
ps->Status = ESC;
break;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停和恢复暂停
pause();
}
else if (KEY_PRESS(VK_F3))
{
if (ps->SleepTime>=80)
{
ps->SleepTime -= 30;
ps->FoodWeight += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
if (ps->FoodWeight > 2)
{
ps->SleepTime += 30;
ps->FoodWeight -= 2;
}
}
//睡眠
Sleep(ps->SleepTime);

    //走一步
SnakeMove(ps);

//检测撞墙
KillByWall(ps);

//检测是否撞到自己
KillBySelf(ps);
} while (ps->Status == OK);
}


void GameEnd(pSnake ps)
{
SetPos(35, 18);
switch (ps->Status)
{
case ESC:
printf("正常退出\n");
break;
case KILL_BYWALL:
printf("很遗憾,撞到墙,游戏结束\n");
break;
case KILL_BYSELF:
printf("很遗憾,咬到自己了,游戏结束\n");
break;
}

//释放贪吃蛇
pSnakeNode cur = ps->pSnake;
while (cur)
{
pSnakeNode next = cur->next;
free(cur);
cur = next;
}
free(ps->pFood);
ps = NULL;
}

void test()
{
int ch = 0;
do
{
Snake snake = { 0 };
GameStart(&snake);
GameRun(&snake);
GameEnd(&snake);
SetPos(35, 18);
printf("再来一局吗:Y/N");
ch=getchar();
getchar();
} while (ch == 'y' || ch == 'Y');
}
int main()
{
setlocale(LC_ALL, "");
    srand((unsigned int)time(NULL));
test();
SetPos(55, 35);
return 0;
}


原文地址:https://blog.csdn.net/2301_80176226/article/details/136124518

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!