16 贪吃蛇

目录

  1. 游戏背景
  2. 游戏效果展示
  3. 基本功能
  4. 技术要点
  5. WIN32 API介绍
  6. 设计与分析
  7. 实现
  8. 参考代码

1. 游戏背景

贪吃蛇是久负盛名的游戏,是一款经典游戏

2. 效果展示

在这里插入图片描述

3. 基本功能

使用c语言在windows环境的控制台模拟实现小游戏贪吃蛇
基本的功能:

  • 地图绘制
  • 吃食物
  • 上下左右移动
  • 撞墙、撞自己死亡
  • 计算得分
  • 加速、减速
  • 暂停游戏

4. 要点

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

5. Win32API介绍

5.1 Win32API

windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮助应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序,所以便称之为 Application Programming Interface,简称API函数。WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口

5.2 控制台程序

平常的黑框程序就是控制台程序

可以使用cmd命令来控制控制台窗口的长宽: 设置控制台窗口的大小,30行,100列

mode con cols=100 lines=30

也可以设置控制台窗口的名字

title 贪吃蛇

这些能在控制台窗口执行的命令,也可以调用c语言函数system执行

#include <stdio.h>
int main()
{//设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列system("mode con cols=100 lines=30");//设置cmd窗⼝名称system("title 贪吃蛇"); return 0;
}

5.3 控制台屏幕上的坐标COORD

COORD是windows api定义的一个结构体,表示一个字符在控制台屏幕上的坐标

typedef struct _COORD{
SHORT X;
SHORT Y;
} COORD,*PCOORD;

给坐标赋值:
COORD pos = { 10, 15 };

5.4 GetStdHandle

用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备

HANDLE GetStdHandle (DWORD nStdHandle) ;

HANDLE hQutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)
hQutput = GetStdHandle(STD_OUTPUT_HANDLE);

5.5 GetConsoleCursorInfo

检测有关指定控制台屏幕缓冲区的光标大小和可见性的信息

BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
)

实例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(用来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;
//获取控制台光标信息
GetConsoleCursorInfo(hOutput, &CursorInfo);

CONSOLE_CURSOR_INFO

这个结构体,包含控制台光标的信息

typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO , *PCONSOLE_CURSOR_INFO;

  • dwSize,由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完全填充单元格到单元底部的水平线条
  • bVisble,游标的可见性,可见设置为true

CursorInfo.bVisible = false; //隐藏控制台光标

5.7 SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置

BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);

COORD pos = { 10, 5 };
HANDLE hOutput = NULL;
//获取标准输出的句柄(用来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置pos
SetConsoleCursorPosition(hOutput, pos);

封装一个设置光标位置的函数

void SetPos(short x, short y)
{COORD pos = { x , y };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置posSetConsoleCursorPosition(hOutput, pos);
}

5.8 GetAsyncKeyState

获取按键情况,GetAsyncKeyState的函数原型如下:

SHORT GetAsyncKeyState(
int vKey;
);

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态

GetAsyncKeyState的返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置1说明,该按键被按过,否则是0

如果判断一个键是否被按过,可以检测这个函数的返回值的最低位是否为1

GetAsyncKeyState(VK) & 0x1 == 1
//宏定义
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? ! : 0)

6. 设计与分析

6.1 地图

如下图
在这里插入图片描述在这里插入图片描述在这里插入图片描述

想在控制台指定位置输出信息,必须知道该位置的坐标

如下图所示,横向的是x轴,从左右次依次增长,纵向是y轴,从上到下依次增长

在这里插入图片描述
在游戏地图上,打印墙体使用宽字符:□,蛇用宽字符:● 食物用宽字符:★
普通的字符是占一个字节的,这类宽字符是占用2个字节

c语言字符默认采用ASCII编码,ASCII字符集采用的是单字节编码,且只使用了单字节中的低7位,最高位是没有使用的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含了128个字符,在英语国家中,128个字符基本够用,但是在其他国家语言中,就无法表示。一些欧洲国家用最高位编入新的符号,比如,法语é的编码是130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,又出现了新的问题。不同的国家有不同的字母,哪怕都是用256个符号的编码方式,同一个码表示的字符确不一样,不管怎么样,0-127表示的符号是一样的,不一样的只是128-255这一段

至于亚洲国家的文字,需要的符号就更多了,汉字就多达10万左右,一个字节肯定不够,就必须使用多个字节表达一个符号。简体中文常见的编码方式是GB2312,使用后两个字节表示一个汉字,理论上最多可以表示256×256=65536个符号

后来为了使c语言国际化,不断加入了国家,用宽字节类型wchar_t和宽字节的输入和输出函数,加入和<locale.h>头文件,提供了设置特定的地区(通常是国家或者特定语言的地理区域)调整程序行为的函数

6.1.1 <locale.h>本地化

<locale.h>提供的函数用于控制c标准库中对于不同的地区会产生不一样行为的部分

在标准中,依赖地区的部分有以下几项:

  • 数字量的格式
  • 货币量的格式
  • 字符集
  • 日期和时间的表现形式

6.1.2 类项

通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分不希望修改,支持堆不同的类项修改,下面的宏指定类项

  • LC_COLLATE
  • LC_CTYPE
  • LC_NUMERIC
  • LC_TIME
  • LC_ALL -针对所有类项修改

6.1.3 setlocale函数

char* setlocale (int category, cosnt char* locale) ;

setlocale 函数用来修改当前地区,可以针对一个类项,也可以针对所有类项

setlocale 的第一个参数可以是前面说明的类型中的一个,那么每次只会影响一个类型,如果第一个参数是LC_ALL,就会影响所有的类项

c标准给第二个参数定义了2种可能取值: “C"和” ",任意程序执行开始,都会隐藏式执行调用

setlocale(LC_ALL, “C” ) ;

当地区设置为C,库函数按正常方式执行,小数点是一个点

想改变地区,只能显示调用函数,用“ ”作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。比如:切换到我们的本地模式后就支持宽字符的输出

setlocale (LC_ALL, “”);

6.1.4 宽字符的打印

#include <stdio.h>
#include <locale.h>int main()
{setlocale(LC_ALL, "");wchar_t ch1 = L'●';wchar_t ch2 = L'★';printf("%c%c", 'a', 'b');printf("%c%c\n", 'a', 'b');wprintf(L"%c",ch1);wprintf(L"%c",ch2);
}

在这里插入图片描述
从结果看出,一个普通字符占一个位置,汉字字符占两个位置,所以打印宽字符地图,需要计算好坐标

在这里插入图片描述

从左上角的00点开始,和控制台边框空出一段间隔,x从2开始打印,y从1开始打印,宽字符一个字符占两列,地图19行44列,从上面的起点算,x从2-45,y从1-19,四周最边的一面为墙

在这里插入图片描述

6.2 蛇身和食物

初始化状态,假设蛇的长度是3,蛇身的每个节点●,在固定的坐标处,y在第一列,横坐标放在地图的中间。需要注意,每个节点的x坐标必须是2的倍数,否则就会和墙体对不齐

食物是在墙体内随机生成一个坐标,x坐标也必须是2的倍数对齐,同时不能和蛇身重合,打印★

6.3 数据结构设计

在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标和下一个结点就行

首先得有一个坐标的结构体

struct Point
{int x;int y;
};

蛇节点的结构体

//蛇身结点
typedef struct _SnakeNode
{struct Point spoint;  //坐标struct _SnakeNode* next;
}SnakeNode, * pNode;

有了蛇身的结构,还需要管理整条蛇的属性信息,比如速度、蛇头等

//蛇属性
typedef struct _Snake
{pNode phead;   //蛇头int score;     //分数int weight;    //权重int speed;     //移动间隔enum Dorection dre;  //方向enum STATUS sta; //状态
}* pSnake;

蛇的方向和状态用枚举出所有可能得结果

//方向
enum Dorection
{UP,DOWN,LEFT,RIGHT,
};
//状态
enum STATUS
{OK = 1,PAUSE,KILL_WALL,KILL_SELF,ESC,
};

6.4 游戏流程设计

在这里插入图片描述

7. 实现

7.1 主逻辑

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "Sanke.h"int main()
{int ch = 0;do{//创建蛇struct _Snake snake;//创建食物struct Point food;//初始化GameInit(&snake, &food);//游戏循环GameRun(&snake, &food);//游戏结束GameEnd(&snake);MoveCursor(16, 11);printf("再来一局吗?(Y/N)");while (_kbhit()){getch();}ch = getchar();//读取换行getchar();} while (ch == 'Y' || ch == 'y');MoveCursor(0, 21);return 0;
}

7.2 初始化信息

void GameInit(pSnake snake, struct Point* food)
{//控制台设置system("mode con cols=82 lines=23");system("title 贪吃蛇");srand(time(0));HideCursor();菜单Menu();

7.2.1 打印菜单界面

开始之前,做一些功能提醒

void Menu()
{//欢迎MoveCursor(30, 10);puts("欢迎来到贪吃蛇游戏");MoveCursor(31, 18);system("pause");system("cls");//功能介绍MoveCursor(15, 8);puts("用 w . s . a . d 来控制蛇的移动,F3是加速,F4是减速");MoveCursor(18, 10);printf("加速能得到更高的分数");MoveCursor(31, 18);system("pause");system("cls");
}

7.2.2 创建地图

将墙打印出来,宽字节打印要用wprintf,字符串前面用L

关键是算好坐标

#define WALL L’□’
//地图大小
#define ROW 19
#define COL 44

void DrawMap()
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x6);//四周for (int i = 0; i < COL; i += 2){MoveCursor(2 + i, 1);wprintf(L"%lc", WALL);MoveCursor(2 + i, 1 + ROW);wprintf(L"%lc", WALL);}for (int i = 0; i < ROW; i++){MoveCursor(2, 2 + i);wprintf(L"%lc", WALL);MoveCursor(COL, 2 + i);wprintf(L"%lc", WALL);}printf("\r\n");
}

在这里插入图片描述

7.2.3 初始化蛇身

蛇的初始长度宏定义,每结对应一个节点,每个节点都有自己的坐标,将所有节点用蛇头节点管理,创建完后打印。设置蛇的速度,方向,分数等

蛇打印的宽字符

#define SNAKE L’●’

//初始化蛇
snake->phead = NULL;
for (int i = 0; i < LENGTH; i++)
{pNode newnode = (pNode)malloc(sizeof(SnakeNode));newnode->next = NULL;newnode->spoint.x = SNAKE_X + i * 2;newnode->spoint.y = SNAKE_Y;//头插法if (snake->phead == NULL){snake->phead = newnode;}else{newnode->next = snake->phead;snake->phead = newnode;}}
snake->score = 0;
snake->weight = 10;
snake->speed = 500;
snake->dre = RIGHT;
snake->sta = OK;

在这里插入图片描述

7.2.4 创建第一个食物

随机生成食物的坐标

  • x坐标必须是2的倍数
  • 食物的坐标不能和蛇身每个节点的坐标重复

创建食物对象,打印食物

食物打印的宽字符

#define FOOD L’★’

首先食物的坐标不能生成在墙上,所以x坐标从4-42,就是先生成0-38的随机数,再加4,y坐标就是2-18,先生成0-16的随机数再加2。再判断是不是在蛇身上,下面是食物坐标的生成

void FoodPoint(struct Point* food , pSnake snake)
{pNode cur = snake->phead;int flag = 0;do{food->x = rand() % (COL - 5) + 4;food->y = rand() % (ROW - 2) + 2;flag = 1;while (cur != NULL){if (food->x == cur->spoint.x && food->y == cur->spoint.y){flag = 0;break;}cur = cur->next;}} while (food->x % 2 != 0 && flag == 1);}

在这里插入图片描述

7.3 游戏运行

右侧打印帮助信息
根据游戏状态检查是否继续,如果状态OK则继续,否则游戏结束

如果继续就要检测按键情况,确定蛇下一步的方向和坐标,是否加速减速暂停退出等,就可以移动蛇了

void GameRun(pSnake snake, struct Point* food)
{//打印帮助信息PrintHelp();DrawMap();DrawSnake(snake);DrawFood(food);//循环do{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);//打印分数MoveCursor(52, 6);printf("总分:%3d\n", snake->score);MoveCursor(51, 8);printf("食物的分值:%4d\n", snake->weight);//检测按键//上 下 左 右 空格 F3if (KEY_PRESS(0x57) && snake->dre != DOWN){snake->dre = UP;}else if (KEY_PRESS(0x53) && snake->dre != UP){snake->dre = DOWN;}else if (KEY_PRESS(0x41) && snake->dre != RIGHT){snake->dre = LEFT;}else if (KEY_PRESS(0x44) && snake->dre != LEFT){snake->dre = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){snake->sta = ESC;}else if (KEY_PRESS(VK_SPACE)){snake->sta = PAUSE;//暂停GamePause(snake);}else if (KEY_PRESS(VK_F3)){if (snake->speed >= 80){snake->speed -= 100;snake->weight += 2;}}else if (KEY_PRESS(VK_F4)){if (snake->weight >= 2){snake->speed += 100;snake->weight -= 2;}}//移动MoveSnake(snake, food);Sleep(snake->speed);} while (snake->sta == OK);
}

7.3.1 KEY_PRESS

检测按键,定义为宏

#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

7.3.2 PrintHelpInfo

void PrintHelp()
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);MoveCursor(49, 12);printf("1.不能穿墙,不能咬到自己");MoveCursor(49, 14);printf("2.用 w.s.a.d 来控制蛇的移动");MoveCursor(49, 16);printf("3.F3是加速,F4是减速");}

在这里插入图片描述

7.3.3 移动蛇

先创建下一个节点,根据移动的方向和蛇头的坐标,设置新蛇身的坐标

确定之后就要分下一个位置是不是食物,是食物就吃掉,刷新分数,重新生成食物不是就移动

将新节点连接蛇结构
移动后判断是否撞墙或撞自身,影响游戏状态结束游戏

void GameRun(pSnake snake, struct Point* food)
{//打印帮助信息PrintHelp();DrawMap();DrawSnake(snake);DrawFood(food);//循环do{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);//打印分数MoveCursor(52, 6);printf("总分:%3d\n", snake->score);MoveCursor(51, 8);printf("食物的分值:%4d\n", snake->weight);//检测按键//上 下 左 右 空格 F3if (KEY_PRESS(0x57) && snake->dre != DOWN){snake->dre = UP;}else if (KEY_PRESS(0x53) && snake->dre != UP){snake->dre = DOWN;}else if (KEY_PRESS(0x41) && snake->dre != RIGHT){snake->dre = LEFT;}else if (KEY_PRESS(0x44) && snake->dre != LEFT){snake->dre = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){snake->sta = ESC;}else if (KEY_PRESS(VK_SPACE)){snake->sta = PAUSE;//暂停GamePause(snake);}else if (KEY_PRESS(VK_F3)){if (snake->speed >= 80){snake->speed -= 100;snake->weight += 2;}}else if (KEY_PRESS(VK_F4)){if (snake->weight >= 2){snake->speed += 100;snake->weight -= 2;}}//移动MoveSnake(snake, food);Sleep(snake->speed);} while (snake->sta == OK);
}void MoveSnake(pSnake snake, struct Point* food)
{pNode newnode = (pNode)malloc(sizeof(SnakeNode));newnode->next = NULL;switch (snake->dre){case UP:newnode->spoint.x = snake->phead->spoint.x;newnode->spoint.y = snake->phead->spoint.y - 1;break;case DOWN:newnode->spoint.x = snake->phead->spoint.x;newnode->spoint.y = snake->phead->spoint.y + 1;break;case LEFT:newnode->spoint.x = snake->phead->spoint.x - 2;newnode->spoint.y = snake->phead->spoint.y;break;case RIGHT:newnode->spoint.x = snake->phead->spoint.x + 2;newnode->spoint.y = snake->phead->spoint.y;break;}//下一个坐标是不是食物if (newnode->spoint.x == food->x && newnode->spoint.y == food->y){EatFood(snake, food);}else{//释放尾结点pNode cur = snake->phead;while(cur->next->next != NULL){cur = cur->next;}//尾结点处打印空MoveCursor(cur->next->spoint.x, cur->next->spoint.y);printf("  ");free(cur->next);cur->next = NULL;}newnode->next = snake->phead;snake->phead = newnode;//检测撞KillWall(snake);KillSelf(snake);DrawSnake(snake);}

吃掉食物更新分数,重新生成食物

void EatFood(pSnake snake, struct Point* food)
{FoodPoint(food, snake);snake->score += snake->weight;DrawFood(food);
}

7.3.4 撞墙

void KillWall(pSnake snake)
{int x = snake->phead->spoint.x;int y = snake->phead->spoint.y;if (x == 2 || x == 44 || y == 1 || y == 19){snake->sta = KILL_WALL;return;}
}

在这里插入图片描述

7.3.5 撞自己

void KillSelf(pSnake snake)
{pNode cur = snake->phead->next;while (cur != NULL){if (cur->spoint.x == snake->phead->spoint.x && cur->spoint.y == snake->phead->spoint.y){snake->sta = KILL_SELF;return;}cur = cur->next;}
}

在这里插入图片描述

7.4 游戏结束

当游戏状态不再是OK或PAUSE的时候,游戏结束,告知结束的原因,并释放蛇身节点

void GameEnd(pSnake snake)
{MoveCursor(18, 9);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);switch (snake->sta){case ESC:printf("退出游戏");break;case KILL_WALL:printf("撞墙死亡");break;case KILL_SELF:printf("撞自己死亡");break;}//释放资源pNode cur = snake->phead;while (cur){pNode del = cur;cur = cur->next;free(del);}
}

8. 参考代码

源.c

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "Sanke.h"int main()
{int ch = 0;do{//创建蛇struct _Snake snake;//创建食物struct Point food;//初始化GameInit(&snake, &food);//游戏循环GameRun(&snake, &food);//游戏结束GameEnd(&snake);MoveCursor(16, 11);printf("再来一局吗?(Y/N)");while (_kbhit()){getch();}ch = getchar();//读取换行getchar();} while (ch == 'Y' || ch == 'y');MoveCursor(0, 21);return 0;
}

Sanke.h

#pragma once//地图大小
#define ROW 19
#define COL 44//各种物体形状
#define WALL L'□'
#define SNAKE L'●'
#define FOOD L'★'//蛇的初始长度
#define LENGTH 3
//蛇初始位置
#define SNAKE_X COL / 2 + 2
#define SNAKE_Y 4//按键
#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
//方向
enum Dorection
{UP,DOWN,LEFT,RIGHT,
};
//状态
enum STATUS
{OK = 1,PAUSE,KILL_WALL,KILL_SELF,ESC,
};
struct Point
{int x;int y;
};
//蛇身结点
typedef struct _SnakeNode
{struct Point spoint;  //坐标struct _SnakeNode* next;
}SnakeNode, * pNode;//蛇属性
typedef struct _Snake
{pNode phead;   //蛇头int score;     //分数int weight;    //权重int speed;     //移动间隔enum Dorection dre;  //方向enum STATUS sta; //状态
}* pSnake;//隐藏光标
void HideCursor();
//移动光标
void MoveCursor(int x, int y);
//菜单界面
void Menu();
//食物坐标随机检测
void FoodPoint(struct Point* food, pSnake snake);
//游戏初始化
void GameInit(pSnake snake, struct Point* food);
//绘制地图
void DrawMap();
//绘制蛇
void DrawSnake(pSnake snake);
//绘制食物
void DrawFood();
//游戏暂停
void GamePause(pSnake snake);
//游戏循环
void GameRun(pSnake snake, struct Point* food);
//移动
void MoveSnake(pSnake snake, struct Point* food);
//吃食物
void EatFood(pSnake snake, struct Point* food);
//打印帮助信息
void PrintHelp();
//检测撞墙
void KillWall(pSnake snake);
//检测撞自己
void KillSelf(pSnake snake);
//游戏结束释放资源
void GameEnd(pSnake snake);

Sanke.c

#include "Sanke.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <locale.h>void HideCursor()
{CONSOLE_CURSOR_INFO info;HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);GetConsoleCursorInfo(handle, &info);info.bVisible = false;SetConsoleCursorInfo(handle, &info);
}void MoveCursor(int x, int y)
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);COORD coord = { x , y };SetConsoleCursorPosition(handle, coord);
}void Menu()
{//欢迎MoveCursor(30, 10);puts("欢迎来到贪吃蛇游戏");MoveCursor(31, 18);system("pause");system("cls");//功能介绍MoveCursor(15, 8);puts("用 w . s . a . d 来控制蛇的移动,F3是加速,F4是减速");MoveCursor(18, 10);printf("加速能得到更高的分数");MoveCursor(31, 18);system("pause");system("cls");
}void FoodPoint(struct Point* food , pSnake snake)
{pNode cur = snake->phead;int flag = 0;do{food->x = rand() % (COL - 5) + 4;food->y = rand() % (ROW - 2) + 2;flag = 1;while (cur != NULL){if (food->x == cur->spoint.x && food->y == cur->spoint.y){flag = 0;break;}cur = cur->next;}} while (food->x % 2 != 0 && flag == 1);}void GameInit(pSnake snake, struct Point* food)
{//控制台设置system("mode con cols=82 lines=23");system("title 贪吃蛇");srand(time(0));HideCursor();菜单Menu();//初始化蛇snake->phead = NULL;for (int i = 0; i < LENGTH; i++){pNode newnode = (pNode)malloc(sizeof(SnakeNode));newnode->next = NULL;newnode->spoint.x = SNAKE_X + i * 2;newnode->spoint.y = SNAKE_Y;//头插法if (snake->phead == NULL){snake->phead = newnode;}else{newnode->next = snake->phead;snake->phead = newnode;}}snake->score = 0;snake->weight = 10;snake->speed = 500;snake->dre = RIGHT;snake->sta = OK;//初始化食物FoodPoint(food, snake);//设置地区setlocale(LC_ALL, "");
}void DrawMap()
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x6);//四周for (int i = 0; i < COL; i += 2){MoveCursor(2 + i, 1);wprintf(L"%lc", WALL);MoveCursor(2 + i, 1 + ROW);wprintf(L"%lc", WALL);}for (int i = 0; i < ROW; i++){MoveCursor(2, 2 + i);wprintf(L"%lc", WALL);MoveCursor(COL, 2 + i);wprintf(L"%lc", WALL);}printf("\r\n");
}void DrawSnake(pSnake snake)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN);pNode cur = snake->phead;while (cur != NULL){MoveCursor(cur->spoint.x, cur->spoint.y);wprintf(L"%lc", SNAKE);  cur = cur->next;}
}void DrawFood(struct Point* food)
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED);MoveCursor(food->x, food->y);wprintf(L"%lc", FOOD);
}void GamePause(pSnake snake)
{while (snake->sta == PAUSE){if (KEY_PRESS(VK_SPACE)){snake->sta = OK;}Sleep(100);}
}void GameRun(pSnake snake, struct Point* food)
{//打印帮助信息PrintHelp();DrawMap();DrawSnake(snake);DrawFood(food);//循环do{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);//打印分数MoveCursor(52, 6);printf("总分:%3d\n", snake->score);MoveCursor(51, 8);printf("食物的分值:%4d\n", snake->weight);//检测按键//上 下 左 右 空格 F3if (KEY_PRESS(0x57) && snake->dre != DOWN){snake->dre = UP;}else if (KEY_PRESS(0x53) && snake->dre != UP){snake->dre = DOWN;}else if (KEY_PRESS(0x41) && snake->dre != RIGHT){snake->dre = LEFT;}else if (KEY_PRESS(0x44) && snake->dre != LEFT){snake->dre = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){snake->sta = ESC;}else if (KEY_PRESS(VK_SPACE)){snake->sta = PAUSE;//暂停GamePause(snake);}else if (KEY_PRESS(VK_F3)){if (snake->speed >= 80){snake->speed -= 100;snake->weight += 2;}}else if (KEY_PRESS(VK_F4)){if (snake->weight >= 2){snake->speed += 100;snake->weight -= 2;}}//移动MoveSnake(snake, food);Sleep(snake->speed);} while (snake->sta == OK);
}void MoveSnake(pSnake snake, struct Point* food)
{pNode newnode = (pNode)malloc(sizeof(SnakeNode));newnode->next = NULL;switch (snake->dre){case UP:newnode->spoint.x = snake->phead->spoint.x;newnode->spoint.y = snake->phead->spoint.y - 1;break;case DOWN:newnode->spoint.x = snake->phead->spoint.x;newnode->spoint.y = snake->phead->spoint.y + 1;break;case LEFT:newnode->spoint.x = snake->phead->spoint.x - 2;newnode->spoint.y = snake->phead->spoint.y;break;case RIGHT:newnode->spoint.x = snake->phead->spoint.x + 2;newnode->spoint.y = snake->phead->spoint.y;break;}//下一个坐标是不是食物if (newnode->spoint.x == food->x && newnode->spoint.y == food->y){EatFood(snake, food);}else{//释放尾结点pNode cur = snake->phead;while(cur->next->next != NULL){cur = cur->next;}//尾结点处打印空MoveCursor(cur->next->spoint.x, cur->next->spoint.y);printf("  ");free(cur->next);cur->next = NULL;}newnode->next = snake->phead;snake->phead = newnode;//检测撞KillWall(snake);KillSelf(snake);DrawSnake(snake);}void EatFood(pSnake snake, struct Point* food)
{FoodPoint(food, snake);snake->score += snake->weight;DrawFood(food);
}void PrintHelp()
{SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);MoveCursor(49, 12);printf("1.不能穿墙,不能咬到自己");MoveCursor(49, 14);printf("2.用 w.s.a.d 来控制蛇的移动");MoveCursor(49, 16);printf("3.F3是加速,F4是减速");}void KillWall(pSnake snake)
{int x = snake->phead->spoint.x;int y = snake->phead->spoint.y;if (x == 2 || x == 44 || y == 1 || y == 19){snake->sta = KILL_WALL;return;}
}void KillSelf(pSnake snake)
{pNode cur = snake->phead->next;while (cur != NULL){if (cur->spoint.x == snake->phead->spoint.x && cur->spoint.y == snake->phead->spoint.y){snake->sta = KILL_SELF;return;}cur = cur->next;}
}void GameEnd(pSnake snake)
{MoveCursor(18, 9);SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);switch (snake->sta){case ESC:printf("退出游戏");break;case KILL_WALL:printf("撞墙死亡");break;case KILL_SELF:printf("撞自己死亡");break;}//释放资源pNode cur = snake->phead;while (cur){pNode del = cur;cur = cur->next;free(del);}
}

参考:汉字字符集编码查询;中⽂字符集编码:GB2312、BIG5、GBK、GB18030、Unicode

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/680931.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MIT-Missing Semester_Topic 3:Editors (Vim) 练习题

文章目录 练习一练习二练习三练习四练习五练习六练习七练习八 本 Topic 的 MIT 讲解网页&#xff08;练习题未给解答&#xff09; 练习一 自行完成 vimtutor。vimtutor 是 Vim 本身附带的一个入门教程&#xff0c;在 shell 中直接输入 vimtutor 便能运行。注意该教程在 8024 大…

【小沐学GIS】基于C++QT绘制三维数字地球Earth(OpenGL)

&#x1f37a;三维数字地球系列相关文章如下&#x1f37a;&#xff1a;1【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;456:OpenGL、glfw、glut&#xff09;第一期2【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;456:OpenGL、glfw、glut&#xff09;第二期3【小沐…

QT:实现图片选择器

一、效果图 二、用到的类 qApp&#xff1a;可以快速获取到项目目录位置。 QSettings &#xff1a;编写config文件&#xff0c;记录上次打开图片的位置&#xff0c;下次打开图片会从上次的位置查找图片。 QPixmap&#xff1a;用于图片的缩放&#xff0c;防止图片过小&#xff0…

DVWA靶场下载安装

DVWA介绍 DVWA 一共包含了十个攻击模块&#xff0c;分别是:Brute Force(暴力破解)、Command Injection(命令行注入)、CSRF(跨站请求伪造)、File Inclusion(文件包含)、File Upload(文件上传)、Insecure CAPTCHA (不安全的验证码)、SQL Injection(SQL注入)、SQL Injection Blin…

【Java程序设计】【C00249】基于Springboot的私人健身与教练预约管理系统(有论文)

基于Springboot的私人健身与教练预约管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的私人健身与教练预约管理系统 本系统分为系统功能模块、管理员功能模块、教练功能模块以及用户功能模块。 系统功能模…

一、部署Oracle

部署Oracle 一、Docker部署1.Oracle11g1.1 测试环境1.1.1 拉取镜像1.1.2 启动容器1.1.3 配置容器环境变量1.1.4 修改sys、system用户密码1.1.5 创建表空间1.1.6 创建用户并授权1.1.5 使用DBeaver测试连接 二、安装包部署 一、Docker部署 1.Oracle11g 1.1 测试环境 当前只能用…

【Spring MVC篇】Cookie和Session的获取 Header的获取

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【Spring MVC】 本专栏旨在分享学习Spring MVC的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; Cookie是客户端保存用…

基于Java (spring-boot)的职工管理系统

一、项目介绍 基于Java (spring-boot)的职工管理系统功能&#xff1a;登录、老板、职员人事经理、职员、部门列表、职工列表、权限修改、查看所有管理、正在审核列表、添加考勤、添加职工、添加奖惩、合同列表、合同信息、等等等。 适用人群&#xff1a;适合小白、大学生、毕业…

360网络安全面试题汇总

防范常见的 Web 攻击 重要协议分布层 arp 协议的工作原理 rip 协议是什么&#xff1f;rip 的工作原理 什么是 RARP&#xff1f;工作原理 OSPF 协议&#xff1f;OSPF 的工作原理 TCP 与 UDP 区别总结 什么是三次握手四次挥手&#xff1f; tcp 为什么要三次握手&#xff1f; dns…

Science重磅_让大模型像婴儿一样学习语言

英文名称: Grounded language acquisition through the eyes and ears of a single child 中文名称: 通过一个孩子的眼睛和耳朵基于实践学习语言 文章: https://www.science.org/doi/10.1126/science.adi1374 代码: https://github.com/wkvong/multimodalbaby 作者: Wai Keen V…

腾讯云4核8G服务器多少钱?轻量和CVM报价2024新版

腾讯云4核8G服务器S5和轻量应用服务器优惠价格表&#xff0c;轻量应用服务器和CVM云服务器均有活动&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;标准型SA2服务器1444.8元一年&#xff0c;轻量应用服务器4核8G12M带宽一…

java之filter过滤器

1、概念 2、过程 3. 实现 4. 参考链接 参考的b站链接

【网工】华为设备命令学习(综合实验一)

实验要求和实验成果如图所示。 LSW2不需要其他配置&#xff0c;其下就一台设备&#xff0c;不需要区分。 LSW3配置如下&#xff1a; <Huawei>sy Enter system view, return user view with CtrlZ. [Huawei]un in en //关闭系统提示信息 Info: Information …

Java之拦截器interceptor

1. 概念 2. 步骤 第一步 第二步 参考资料 https://www.bilibili.com/video/BV1m84y1w7Tb?p168&vd_source705343a89f38d5c0d895383ccf38a5d6

Microsoft OneNote 图片文字提取

Microsoft OneNote 图片文字提取 1. 文件 -> 新建 -> 我的电脑 -> 名称 -> 位置 -> 创建笔记本2. 插入图片​​​3. 复制图片中的文本References 1. 文件 -> 新建 -> 我的电脑 -> 名称 -> 位置 -> 创建笔记本 ​ 2. 插入图片 ​​​3. 复制图片…

有趣儿的组件(HTML/CSS)

分享几个炫酷的组件&#xff0c;起飞~~ 评论区留爪&#xff0c;继续分享哦~ 文章目录 1. 按钮2. 输入3. 工具提示4. 单选按钮5. 加载中 1. 按钮 HTML&#xff1a; <button id"btn">Button</button>CSS&#xff1a; button {padding: 10px 20px;text-tr…

使用Docker快速部署MySQL

部署MySQL 使用Docker安装&#xff0c;仅仅需要一步即可&#xff0c;在命令行输入下面的命令 docker run -d \--name mysql \-p 3306:3306 \-e TZAsia/Shanghai \-e MYSQL_ROOT_PASSWORD123456 \mysql MySQL安装完毕&#xff01;通过任意客户端工具即可连接到MySQL. 当我们执…

计算机二级C语言的注意事项及相应真题-4-程序修改

目录&#xff1a; 31.逐个比较p、q所指两个字符串对应位置中的字符&#xff0c;把ASCII值大或相等的字符依次存放到c所指数组中&#xff0c;形成一个新的字符串32.求矩阵&#xff08;二维数组)a[N][N]中每行的最小值&#xff0c;结果存放到数组b中33.将一个十进制整数转换成r(二…

那些 C语言指针 你不知道的小秘密 (完结篇)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能…

【GO语言卵细胞级别教程】05.项目创建和函数讲解

感谢&#xff01;点点赞和评论呀&#xff01;我将继续更新 目录&#xff1a; 感谢&#xff01;点点赞和评论呀&#xff01;我将继续更新0.创建项目1.函数的引入2.注意事项3.详细介绍3.1 形参介绍 4.导入包4.1 基本知识4.2 注意事项 0.创建项目 创建目录 执行命令加载模块 cd 0…