当前位置: 首页 > news >正文

C语言实现贪吃蛇

贪吃蛇小游戏的实现

  • 讲解
    • 1.Win32 API介绍
      • 1.1控制台程序(system())
      • 1.2控制台屏幕上的坐标CDDRD
      • 1.3 GetStdHandle
      • 1.4 GetConsoleCursorInfo
      • 1.5 SetConsoleCursorInfo
      • 1.6 SetConsoleCursorPostion
      • 1.7 GetAsyncKeyState
    • 2.游戏设计
      • 2.1地图
      • 2.2蛇身和食物
      • 2.3数据结构设计
      • 2.4游戏流程设计
    • 3.核心逻辑实现分析
      • 3.1游戏主逻辑
      • 3.2 GameStart
      • 3.3 GameRun
      • 3.4 GameEnd
  • 完整代码
    • test.c
    • snake.h
    • snake.c
  • 补充改进

实现贪吃蛇游戏的一些基本功能:地图绘制、蛇吃食物、死亡功能、加速减速、计分、暂停等。

讲解

1.Win32 API介绍

应用程序编程接口。

1.1控制台程序(system())

mode:设置控制台窗口的长宽。
mode con cols=100 lines=30
title:设置控制台窗口的名字。
title 贪吃蛇
图上控制台的大小和标题均被修改

1.2控制台屏幕上的坐标CDDRD

定义了x和y的一个结构体,表示一个字符在控制台屏幕缓冲区上的坐标,(0,0)位于顶部左侧单元格。
COORD pos = { 10,15 };//eg

1.3 GetStdHandle

用于从一个特定的标准设备中取得一个句柄,使用这个句柄可以操作设备。(类似于鼠标)

	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);

1.4 GetConsoleCursorInfo

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

	CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息

CONSOLE_CURSOR_INFO是一个有关控制台光标的信息的结构体。包含两个属性:

  • dwSize:由光标填充的字符单元格的百分比。
  • bVisible:游标的可见性(布尔值)。

1.5 SetConsoleCursorInfo

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

	SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态

由图可见,dwSize=100则光标的大小填满了一整个字节

1.6 SetConsoleCursorPostion

设置指定控制台屏幕缓冲区中的光标位置。

	COORD pos = {10,5};SetConsoleCursorPostion(houtput, pos);

可按照自己希望修改光标位置

1.7 GetAsyncKeyState

获取按键情况。
如果返回值的最低位是1则说明该按键被按过,否则为0。

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

传递的参数为虚拟键码,可查表进行传值。

2.游戏设计

2.1地图

宽字符
大小:两个字节(传统字符),类型:wchar_t,头文件:<locale.h>
setlocale函数

	setlocale(LC_ALL,"C");//正常模式setlocale(LC_ALL," ");//切换到本地环境

宽字符的打印
两个普通字符大小等于一个宽字符大小
注意宽字符在定义和打印时均要在前面加上大写符号“L”,用“%lc”打印。
由图可解释,第一次打印时,长和宽设置一样的数字为什么不是正方形,这里宽也就是行(y)的数字设置时注意×2才等于另一个数值。
假设实现一个27行、58列的棋盘,外面一圈是墙体。

2.2蛇身和食物

蛇身:初始化定义蛇长度为5(宽字符),所以必须是2的倍数;
食物:随机生成,也得是2的倍数,不能和蛇的身体重合。

2.3数据结构设计

蛇每吃一个食物,身体就会长长一节,互相连接着,可用链表表示存储蛇的信息。

//蛇身的节点类型
typedef struct SnakeNode
{//坐标int x;int y;//指向下一个节点的指针struct SnakeNode* next;
}SnakeNode,* pSnakeNode;//typedef struct 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;
//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};
//蛇的状态
enum GAME_STATUS
{OK,//正常KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自己END_NORMAL//正常退出
};

2.4游戏流程设计

主要分为游戏开始、游戏运行、游戏结束三部分。
GameStart:

  • 设置游戏窗口的大小
  • 设置窗口的名字
  • 隐藏屏幕光标
  • 打印欢迎界面
  • 创建地图
  • 初始化蛇身
  • 创建食物
    GameRun:
  • 右侧打印帮助信息
  • 打印当前已获得分数和每个食物的分数
  • 获取按键情况
    1.根据蛇头的坐标和方向,计算下一个节点的坐标
    2.判断下一个节点是否是食物
    3.是食物就吃掉
    4.不是食物,往前一步,尾巴删除一节
    4.判断是否撞墙
    5.判断是否撞上自己
  • 根据按键情况移动蛇
  • 循环,直到游戏是结束状态
    GameEnd:
  • 告知游戏结束的原因
  • 释放蛇身节点

接下来我将根据上述逻辑开始实现每个部分的功能。

3.核心逻辑实现分析

3.1游戏主逻辑

#include<stdio.h>void test()
{int ch = 0;srand((unsigned int)time(NULL));do{system("cls");Snake snake = {0};GameStart(&snake);GameRun(&snake);GameEnd(&snake);SetPos(20,15);printf("再来一局吗?(Y/N):");ch = getchar();getchar();//清理\n}while(ch == 'Y');SetPos(0,27);
}int main()
{//修改当前地区为本地模式,为了支持中文宽字符的打印setlocale(LC_ALL," ");//测试逻辑test();return 0;
}

上述游戏的主要三个部分的函数都采用了传址调用,才能影响变量,函数使用一级指针接收地址,两个结构体指针在定义时已经设置好,则在下面的函数需要时直接使用。

3.2 GameStart

  • 设置游戏窗口的大小
  • 设置窗口的名字
  • 隐藏屏幕光标
void GameStart(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);//打印欢迎界面WelcomeToGame();//打印地图CreateMap();//初始化蛇InitSnake(ps);//创造第一个食物CreateFood(ps);
}
  • 打印欢迎界面
void WelcomeToGame()
{SetPos(40,15);printf("欢迎来到贪吃蛇小游戏!");SetPos(40,25);system("pause");system("cls");SetPos(25,12);printf("用 ↑ ↓ ← → 分别控制蛇的移动,F3为加速,F4为减速\n");SetPos(25,13);printf("加速将能得到更高的分数。\n");SetPos(40,25);system("pause");system("cls");
}

注意:以上坐标位置的设置均是为了观感整洁好看,可自行调整。

  • 创建地图
    算好坐标(易错点)打印墙体。
    在这里插入图片描述
void CreateMap()
{int i = 0;//上SetPos(0,0);for(i=0;i<58;i+=2){wprintf(L"%c",WALL);}//下SetPos(0,26);for(i=0;i<58;i+=2){wprintf(L"%c",WALL);}//左for(i=1;i<26;i++){SetPos(0,i);wprintf(L"%c",WALL);}//右for(i=1;i<26;i++){SetPos(56,i);wprintf(L"%c",WALL);}
}	
  • 初始化蛇身
    蛇最开始长度为5节,创建完节点后依次打印出来。
    初始位置:(24,5);
    游戏状态:OK;
    移动速度:200毫秒;
    初试成绩:0;
    每个食物的分数:10
void InitSnake(pSnake ps)
{pSnakeNode = NULL;int i = 0;//头插法for(i=0;i<5;i++){//创建蛇身的节点cur = (pSnakeNode)malloc(sizeof(SnakeNode));if(cur == NULL){perror("InitSnake()::malloc()");return;}//设置坐标cur->next = NULL;cur->x = POS_X + i * 2;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->_sleep_time = 200;ps->_score = 0;ps->_status = OK;ps->_dir = RIGHT;ps->_food_weight = 10;
}
  • 创建第一个食物
    随机生成 , 2的倍数 , 不能和蛇身重复 , 打印
void CreateFood(pSnake ps)
{int x = 0;int y = 0;
again://产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对齐do{x = rand()%53+2;//2~54y = rand()%25+1;//1~25}while(x % 2 != 0)//如果不符合2的倍数的要求则继续生成pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针//食物不能和蛇身冲突while(cur){if(cur->x == x && cur->y == y){goto again;}cur = cur->next;}pSnake pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//创建食物if(pFood == NULL){perror("CreateFood::malloc()");return;}else{pFood->x = x;pFood->y = y;SetPos(pFood->x,pFood->y);wprintf(L"%c",FOOD);ps->_pFood = pFood;}
}

3.3 GameRun

  • 虚拟按键:
    上:VK_UP
    下:VK_DOWN
    左:VK_LEFT
    右:VK_RIGHT
    空格:VK_SPACE
    ESC:VK_ESCAPE
    F3:VK_F3
    F4:VK_F4
void GameRun()
{//打印右侧提示信息PrintHelpInfo();do//由于do-while语句最后执行判断会先执行其中的打印语句故选用{SetPos(64,10);printf("得分:%d",ps->_score);printf("每个食物得分:%d分",ps->_food_weight);if(KEY_PRESS(VK_UP) && ps->_dir != DOWN)//只有当发出向上走的命令并且蛇本来不是往下走时,执行向上的操作//这里不能写成"本来往下,但是往上指令所以不执行"的语句,else还有很多情况{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_SPACE)){pause();}else if(KEY_PRESS(VK_ESCAPE)){ps->_status = END_NORMAL;break;}else if(KEY_PRESS(VK_F3))//加速{if(ps->_sleep_time >= 100){ps->_sleep_time -= 20;ps->_food_weight += 1;}}else if(KEY_PRESS(VK_F4)){if(ps->_sleep_time <= 300){ps->_sleep_time += 20;ps->_food_weight -=1;}}//蛇每次移动之间要有休眠的时间Sleep(ps->_sleep_time);SnakeMove(ps);}while(ps->_status == OK);
}				
void PrintHelpInfo()
{SetPos(64,15);printf("不能穿墙,不能咬到自己\n");SetPos(64,16);printf("用 ↑.↓.←.→ 分别控制蛇的移动\n");SetPos(64, 17);printf("F3 为加速,F4 为减速\n");SetPos(64, 18);printf("ESC :退出游戏;space :暂停游戏\n");SetPos(64, 20);printf("星光熠熠");
  • 蛇身移动
    先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。
    在这里插入图片描述
    确定了下一个位置后,看下一个位置是否是食物,是食物就在蛇头前面加一个节点,如果不是食物就吃掉食物并释放最后一个节点(空格覆盖食物)。
    蛇身移动后,判断此移动是否会造成撞墙或者撞自己,从而更新游戏的状态。
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//如果不是食物{NotFood(pNextNode,ps);}KillByWall(ps);KillBySelf(ps);
}	
//pn是下一个节点的地址
//ps维护蛇的指针
int NextIsFood(pSnakeNode pn,pSnake ps)
{return (pn->x == ps->_pFood->x) && (pn->y == ps->_pFood->y);
}
void EatFood(pSnakeNode pn,pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;//打印蛇pSnakeNode cur = ps->_pSnake;while(cur){SetPos(cur->x,cur->y);wprintf(L"%c",BODY);cur = cur->next;}ps->_score += ps->_food_weight;//释放食物节点free(ps->_pFood);//创建新的食物CreateFood(ps);
}

不是食物的易错点:释放最后一个节点后,还需将指向最后一个节点的指针改为NULL,也就是最后一个节点的前一个节点。
在这里插入图片描述

void NotFood(pSnakeNode pn,pSnake ps)
{//头插法pn->next = ps->_pSnake;ps->_pSnake = pn;//打印蛇pSnakeNode cur = ps->_pSnake;while(cur->next->next){SetPos(cur->x,cur->y);wprintf(L"%c",BODY);cur = cur->next;}//最后一个位置打印空格,然后释放节点SetPos(cur->next->x,cur->next->y);printf("  ");free(cur->next);cur->next = NULL;
}
int 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;return 1;}return 0;
}
int KillBySelf(pSnake ps)
{pSnakeNode cur = ps->_pSnake->next;while(cur){if((ps->_pSnake->x == cur->x) && (ps->_pSnake->y == cur->y)){ps->_status = KILL_BY_SELF;return 1;}cur = cur->next;}return 0;
}

3.4 GameEnd

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

void GameEnd(pSnake ps)
{pSnakeNode cur = ps->_pSnake;SetPos(24,12);switch(ps->_status){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("您撞上自己了,游戏结束!\n");break;case KILL_BY_WALL:printf("您撞墙了,游戏结束!\n");break;}//释放蛇身的节点while(cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

完整代码

一共分为三个文件。

test.c

#include"snake.h"//完成的是游戏的测试逻辑
void test()
{int ch = 0;do{system("cls");//创建贪吃蛇Snake snake = { 0 };//初始化游戏//1. 打印环境界面//2. 功能介绍//3. 绘制地图//4. 创建蛇//5. 创建食物//6. 设置游戏的相关信息GameStart(&snake);//运行游戏GameRun(&snake);//结束游戏 - 善后工作GameEnd(&snake);SetPos(20, 15);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;
}

snake.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<locale.h>
#include<time.h>
#include<Windows.h>
#include<stdbool.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 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 GameStart(pSnake ps);//欢迎界面的打印
void WelcomeToGame();//创建地图
void CreateMap();//初始化蛇身
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//游戏运行的逻辑
void GameRun(pSnake ps);//蛇的移动-走一步
void SnakeMove(pSnake ps);//判断下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);//下一个位置是食物,就吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);//下一个位置不是食物
void NotFood(pSnakeNode pn, pSnake ps);//检测蛇是否撞墙
void KillByWall(pSnake ps);//检测蛇是否撞到自己
void KillBySelf(pSnake ps);//游戏善后的工作
void GameEnd(pSnake ps);

snake.c

#include "snake.h"void SetPos(short x, short y)
{//获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { x, y };SetConsoleCursorPosition(houtput, pos);
}void WelcomeToGame()
{SetPos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");SetPos(42, 20);system("pause");system("cls");SetPos(25, 14);wprintf(L"用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");SetPos(25, 15);wprintf(L"加速能够得到更高的分数\n");SetPos(42, 20);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 InitSnake(pSnake ps)
{int i = 0;pSnakeNode cur = NULL;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc()");return;}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->_score = 0;ps->_food_weight = 10;ps->_sleep_time = 300;//单位是毫秒ps->_status = OK;}void CreateFood(pSnake ps)
{int x = 0;int y = 0;//生成x是2的倍数//x:2~54//y: 1~25
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x和y的坐标不能和蛇的身体坐标冲突pSnakeNode cur = ps->_pSnake;while (cur){if (x == cur->x && y == cur->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;pFood->next = NULL;SetPos(x, y);//定位位置wprintf(L"%lc", FOOD);ps->_pFood = pFood;
}void GameStart(pSnake ps)
{//0. 先设置窗口的大小,再光标隐藏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);//设置控制台光标状态//1. 打印环境界面和功能介绍WelcomeToGame();//2. 绘制地图CreateMap();//3. 创建蛇InitSnake(ps);//4. 创建食物CreateFood(ps);
}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"星光熠熠");
}#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 (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y)
//		return 1;
//	else
//		return 0;
//}int NextIsFood(pSnakeNode pn, pSnake ps)
{return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);//注意这里的pn指向的是蛇身
}void EatFood(pSnakeNode pn, pSnake ps)
{//头插法ps->_pFood->next = ps->_pSnake;ps->_pSnake = ps->_pFood;//释放下一个位置的节点(pn本来指向的是蛇头的下一个节点,现在吃了食物释放掉这个节点)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 NotFood(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->next->x, cur->next->y);printf("  ");//释放最后一个结点free(cur->next);//把倒数第二个节点的地址置为NULLcur->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;break;}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{NotFood(pNextNode, ps);}//检测蛇是否撞墙KillByWall(ps);//检测蛇是否撞到自己KillBySelf(ps);
}void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//打印总分数和食物的分值SetPos(64, 10);printf("总分数:%d\n", ps->_score);SetPos(64, 11);printf("当前食物的分数:%2d\n", 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_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;}}SnakeMove(ps);//蛇走一步的过程Sleep(ps->_sleep_time);} while (ps->_status == OK);
}void GameEnd(pSnake ps)
{SetPos(24, 12);switch (ps->_status){case END_NORMAL:wprintf(L"您主动结束游戏\n");break;case KILL_BY_WALL:wprintf(L"您撞到墙上,游戏结束\n");break;case KILL_BY_SELF:wprintf(L"您撞到了自己,游戏结束\n");break;}//释放蛇身的链表pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}
}

补充改进

一些可以创新的点:

  • 穿墙设定;
  • 食物分类;
  • 多个实物;
  • 蛇碰到食物的一半也会吃掉…
    (请自行补充)…

http://www.mrgr.cn/news/93571.html

相关文章:

  • 高考數學。。。
  • 200W数据需要去重,如何优化?
  • 20250306-笔记-精读class CVRPEnv:step(self, selected)
  • Flink深入浅出之03:状态、窗口、checkpoint、两阶段提交
  • FPGA学习篇——Verilog学习3(关键字+注释方法+程序基本框架)
  • 蓝桥杯单片机——第十五届蓝桥杯省赛
  • STM32之I2C硬件外设
  • C语言100天练习题【记录本】
  • STM32之硬件SPI
  • 苦瓜书盘官网,免费pdf/mobi电子书下载网站
  • 100天精通Python(爬虫篇)——第115天:爬虫在线小工具_Curl转python爬虫代码工具(快速构建初始爬虫代码)
  • Kubernetes Pod网络组件解析与选型指南
  • python从入门到精通(二十五):文件操作和目录管理难度分级练习题
  • 【华三】STP端口角色与状态深度解析
  • MySQL------存储引擎和用户和授权
  • 从0开始的操作系统手搓教程25:使用环状缓冲区来让我们的键盘驱动真正的有作用起来
  • 200W数据去重入库的几种方法及优缺点
  • STM32-I2C通信协议
  • Browser Use+DeepSeek的使用教程
  • LTC6804、LTC6811、LTC6813的使用