C语言实现贪吃蛇游戏

devtools/2025/1/15 14:11:56/

贪吃蛇游戏分析

贪吃蛇游戏简介

贪吃蛇与扫雷游戏、俄罗斯方块一样,都是久负盛名的小游戏学会用C语言实现贪吃蛇游戏,有助于我们更好的理解C语言的语法

贪吃蛇所涉及到的技术包含函数、结构体、枚举、指针、Win32API等。

其中除了Win32API之外,其他都是C语言的语法,当然除了这些之外,还包含C语言的其他一些基础语法,这里我们不过多赘述。

Win32API

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

由于Win32API包含很多的函数,而我们在贪吃蛇游戏实现的过程中,只需要用到Win32API中的部分函数,所以我们只对游戏实现过程中需要用到的Win32API函数进行讲解,其余函数不过多赘述

GetStdHandle

GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。

实例:

GetConsoleCursonInfo

检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息。

实例:

CONSOLE_CURSOR_INFO

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

  • dwSize,由光标填充的字符单元格的百分⽐。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的⽔平线条。
  • bVisible,游标的可⻅性。 如果光标可⻅,则此成员为 TRUE。

SetConsoleCursonInfo

设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。

实例:

控制台屏幕上的坐标COORD

COORD 是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系 (0,0) 的原点位于缓冲区的顶部左侧单元格。

COORD类型的声明:

给坐标赋值:

SetConsoleCursorPosition

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

实例:

SetPos:封装⼀个设置光标位置的函数

GetAsyncKeyState

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

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

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

如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.

虚拟键码

游戏界面

初始页面:

功能介绍:

游戏运行界面:

贪吃蛇游戏实现

根据前面对贪吃蛇游戏的分析,我们知道了完成该游戏所需要的C语言技能及相关的Win32API函数,最后也展示了游戏运行过程中的的各种界面,接下来我们将游戏的整体运行分为三个部分(三个函数),分别为游戏初始化、游戏运行、游戏结束

游戏初始化

游戏初始化的主要功能是设置游戏的欢迎界面、对游戏功能进行介绍,创建墙、创建蛇、创建食物

当然在设置前面介绍的功能之前,我们还需要对蛇身结点的结构、蛇的结构、控制台的大小,控制台的标题以及控制台的光标进行设置,如下图:

Welcome()函数

welcome函数主要是对游戏的初始界面进行设置,包含欢迎界面功能介绍,如下图:

运行效果如下:

CreateMap()函数

CreateMap()函数主要是对游戏中的墙体进行绘制,在绘制墙体之前我们还需要移动控制台的光标,由于墙体使用宽字符进行绘制,宽字符占用两个字节的空间,且使用宽字符需要进行本地化,具体实现如下:

效果如下:

CreateSnake()函数

游戏初始化过程中,我们还需要创建蛇,由于蛇的身体使用宽字符进行绘制,宽字符占用两个字节的空间,所以我们蛇身的x坐标只能为偶数,除此之外还需要对蛇的其他信息进行设置,如图:

//创建蛇
void CreateSnake(pSnake ps)
{int i = 0;for (i = 0; i < 5; i++){pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pnextnode == NULL){perror("CreateSnake>malloc");return;}pnextnode->x = POS_X + 2 * i;pnextnode->y = POS_Y;pnextnode->next = NULL;//头插if (ps->_pSnake == NULL){ps->_pSnake = pnextnode;}else{pnextnode->next = ps->_pSnake;ps->_pSnake = pnextnode;}}//打印蛇pSnakeNode 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;}

实现效果如下:

CreateFood()函数

除了对蛇的身体进行初始化外,还需要创建食物,由于食物是在一个范围内随机生成的,因此还需要使用随机数,同样食物也是使用宽字符,占用两个字节的空间,食物的x坐标也只能为偶数,且食物不能与蛇的身体重合,如图:

//创建食物
void CreateFood(pSnake ps)
{int x = 0;int y = 0;again://生成随机坐标do{x = 2 + rand() % 53;y = 1 + rand() % 25;} while (x % 2 != 0);//判断是否与蛇重合pSnakeNode cur = ps->_pSnake;while (cur) {if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//创建食物结点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood>malloc");return;}pFood->x = x;pFood->y = y;SetPos(x, y);wprintf(L"%lc", FOOD);ps->_pFood = pFood;}

实现效果如下:

游戏运行

游戏的运行包含:计算总得分和当前食物的分数、打印提示信息、检测按键情况、蛇走一步的过程、蛇走一步后的休眠时间等,还需要对蛇当前的状态进行判断,如图:

//游戏运行
void GameRun(pSnake ps)
{//打印提示信息PrintTip();do{SetPos(62, 10);printf("总得分:%d", ps->_score);SetPos(62, 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_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 = END_NORMAL;}else if (KEY_PRESS(VK_SPACE)){//暂停Pause();}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;}}//走一步SnakeMove(ps);//休息一下Sleep(ps->_Sleep_Time);} while (ps->_status == OK);
}

检测按键:

暂停:

PrintTip()函数

该函数用来打印提示信息,如图:

效果如下:

SnakeMove()函数

该函数是蛇走一步的过程,需要创建下一个结点,判断当前按键的方向,设置下一个结点的x坐标和y坐标,判断下一个结点是不是食物,若是食物就吃掉食物,若不是食物就头插下一个结点,然后将最后一个结点打印成空格并释放掉,最后还要判断蛇是否撞墙或者撞到自己,如图:

//走一步
void SnakeMove(pSnake ps)
{//为下一个结点分配内存pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pnextnode == NULL){perror("SnakeMove>malloc");return;}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);
}

NextIsFood()函数

该函数用来判断下一节点是否为食物,是食物返回1,不是食物返回0,如图:

EatFood()函数

该函数用来吃掉食物,即将下一个结点头插到蛇身中并打印蛇身,如图:

NoFood()函数

如果下一个结点不是食物,就使用该函数,即头插下一个结点,然后将最后一个结点打印成空格并释放掉,如图:

KILLBYWALL()函数

该函数用来判断蛇是否撞到墙,如果撞到墙,就修改蛇的状态,如图:

KILLBYSELF()函数

该函数用来判断蛇是否撞到自己,如果撞到自己,就修改蛇的状态,如图:

游戏结束

游戏运行结束后,我们就需要进行一些善后工作,我们可以根据蛇的状态判断游戏是怎样退出的,比如撞到墙、撞到自己或者主动退出,但是无论是以何种方式退出游戏的,最后都要把动态分配的空间释放掉,如图:

贪吃蛇游戏最终代码

头文件Snake.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <stdbool.h>
#include <locale.h>
#include <time.h>#define POS_X 24
#define POS_Y 5#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'//蛇的方向
enum DIRECTION
{UP,DOWN,LEFT,RIGHT
};//蛇的状态
enum 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;int _score;int _Food_Weight;int _Sleep_Time;enum DIREWCTION _dir;enum STATUS _status;
}Snake, * pSnake;//游戏初始化
void GameInit(pSnake ps);//欢迎界面
void Welcome();//移动光标
void SetPos(short x, short y);//打印地图
void CreateMap();//创建蛇
void CreateSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//游戏运行
void GameRun(pSnake ps);//打印提示信息
void PrintTip();//暂停
void Pause();//走一步
void SnakeMove(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 GameEnd(pSnake ps);

源文件Snake.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Snake.h"//移动光标
void SetPos(short x, short y)
{HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x,y };SetConsoleCursorPosition(houtput, pos);
}//欢迎界面
void Welcome()
{SetPos(35, 14);printf("欢迎来到贪吃蛇小游戏");SetPos(38, 24);system("pause");system("cls");SetPos(28, 14);printf("用↑.↓.←.→分别控制蛇的移动,F3为加速,F4为减速");SetPos(35, 15);printf("加速将能得到更高的分数");SetPos(38, 24);system("pause");system("cls");
}//打印地图
void CreateMap()
{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 CreateSnake(pSnake ps)
{int i = 0;for (i = 0; i < 5; i++){pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pnextnode == NULL){perror("CreateSnake>malloc");return;}pnextnode->x = POS_X + 2 * i;pnextnode->y = POS_Y;pnextnode->next = NULL;//头插if (ps->_pSnake == NULL){ps->_pSnake = pnextnode;}else{pnextnode->next = ps->_pSnake;ps->_pSnake = pnextnode;}}//打印蛇pSnakeNode 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 CreateFood(pSnake ps)
{int x = 0;int y = 0;again://生成随机坐标do{x = 2 + rand() % 53;y = 1 + rand() % 25;} while (x % 2 != 0);//判断是否与蛇重合pSnakeNode cur = ps->_pSnake;while (cur) {if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//创建食物结点pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood>malloc");return;}pFood->x = x;pFood->y = y;SetPos(x, y);wprintf(L"%lc", FOOD);ps->_pFood = pFood;}//游戏初始化
void GameInit(pSnake ps)
{//设置窗口system("mode con cols=100 lines=32");system("title 贪吃蛇");//隐藏光标HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO cursor_info;GetConsoleCursorInfo(houtput, &cursor_info);cursor_info.bVisible = false;SetConsoleCursorInfo(houtput, &cursor_info);//欢迎界面Welcome();//打印地图CreateMap();//创建蛇CreateSnake(ps);//创建食物CreateFood(ps);}//打印提示信息
void PrintTip()
{SetPos(62, 15);printf("不能穿墙,不能咬到自己");SetPos(62, 16);printf("用↑.↓.←.→分别控制蛇的移动");SetPos(62, 17);printf("F3为加速,F4为减速");SetPos(62, 18);printf("按ESC退出游戏,按space暂停游戏");}#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)
{if (pn->x == ps->_pFood->x && pn->y == ps->_pFood->y){return 1;}else{return 0;}
}//下一步是食物
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->x, cur->y);wprintf(L"%lc", BODY);//将最后一个结点打印成空格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;}cur = cur->next;}
}//走一步
void SnakeMove(pSnake ps)
{//为下一个结点分配内存pSnakeNode pnextnode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pnextnode == NULL){perror("SnakeMove>malloc");return;}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)
{//打印提示信息PrintTip();do{SetPos(62, 10);printf("总得分:%d", ps->_score);SetPos(62, 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_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 = END_NORMAL;}else if (KEY_PRESS(VK_SPACE)){//暂停Pause();}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;}}//走一步SnakeMove(ps);//休息一下Sleep(ps->_Sleep_Time);} while (ps->_status == OK);
}//游戏结束-善后工作
void GameEnd(pSnake ps)
{SetPos(22, 12);switch (ps->_status){case END_NORMAL:printf("您主动退出,游戏结束!");break;case KILL_BY_WALL:printf("您撞到墙,游戏结束!");break;case KILL_BY_SELF:printf("您撞到自己,游戏结束!");break;}//释放蛇身pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode next = cur->next;free(cur);cur = next;}}

测试文件test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Snake.h"void test()
{int ch = 0;do{system("cls");//创建蛇Snake snake = { 0 };//游戏初始化GameInit(&snake);//游戏运行GameRun(&snake);//游戏结束-善后工作GameEnd(&snake);SetPos(22, 13);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;
}


http://www.ppmy.cn/devtools/105669.html

相关文章

2024年河南省成人高考报名指南

2024年河南省成人高考报名指南 河南省成人高考预计9月初g网报名&#xff0c;一般一周时间结束&#xff0c;预计到9月中旬报名截止‼不清楚报名流程同学看过来&#xff0c;今天老师给大家详细介绍一下&#xff01; 想参加河南成人高考需要什么条件&#xff0c;具体的报名流程是什…

Linux系统中的时间管理

文章目录 前言1. Linux系统中的时间管理1.1 系统时钟&#xff08;System Clock&#xff09;1.2 硬件时钟&#xff08;Real Time Clock, RTC&#xff09;1.3 时间命令1.4 时间同步1.5 时区处理1.6 夏令时1.7 进程时间 2. 为什么有的Linux系统是12小时制&#xff1f; 前言 when&…

Xml 映射文件中常见的标签

一、动态 SQL 标签 MyBatis 提供了一系列标签来处理动态 SQL&#xff0c;它们能够根据传入的参数生成不同的 SQL 语句。这些标签包括&#xff1a; 1. **<if> 标签** <if> 标签用于根据条件判断是否包含某段 SQL 语句。可以使用它来实现动态拼接 SQL。 xml <…

iOS 中,用户点击一个按钮到响应的全部流程

在 iOS 中&#xff0c;当用户点击一个按钮&#xff08;或其他 UI 控件&#xff09;时&#xff0c;会触发一系列复杂的操作流程&#xff0c;从硬件到软件&#xff0c;再到应用层的事件处理。以下是从用户点击一个按钮到应用响应的完整流程&#xff1a; 硬件层&#xff1a;触摸事…

Java导出图片到excel

1、例如你有这样一个集合&#xff0c;具体结合你的业务场景 Data public class Student {/*** 姓名*/private String xh;/*** 学号*/private String xm;/*** 照片*/private byte[] zp; }2、相关代码 RequestMapping(value "/quereImgByPkid/{pkid}",method Requ…

C++ 满足某些条件可以当成rust 用

c 内存安全性较差&#xff0c;但是满足某些条件其实也挺安全的 1.所有堆上分配的内存包裹在容器里&#xff0c;弃用指针。感觉容器的栈挂堆&#xff08;stack point to heap&#xff09;的结构就是和rust学的 2.灵活使用std::move()转移所有权(ownership)提高效率。 3.引用注意…

2.SpringBoot项目pom.xml文件配置

1.pom.xml示例&#xff08;初始项目创建时生成&#xff09; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schem…

VS2022使用指定的LLVM版本

LLVM下载地址&#xff1a;Releases llvm/llvm-project GitHub LLVM/Clang toolsets for Visual Studio 2022, 2019, 2017, 2015, 2013, 2012 and 2010. GitHub - zufuliu/llvm-utils: LLVM/Clang toolsets for Visual Studio 2022, 2019, 2017, 2015, 2013, 2012 and 2010.…