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.rhkb.cn/news/29736.html

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

相关文章

游戏引擎学习第142天

今天的计划 欢迎来到这个游戏开发项目&#xff0c;我们将从零开始编写一个完整的游戏&#xff0c;并且不会使用任何现成的库或引擎。整个开发过程中涉及的所有代码都会被完整展示&#xff0c;包括游戏运行所需的每一个细节。无论是哪款游戏&#xff0c;最终都需要有人编写底层…

Manus全球首个通用Agent,Manus AI:Agent应用的ChatGPT时刻

文章目录 前言Manus AI: 全球首个通用AgentManus AI: 技术架构与创始人经历AI Agent的实现框架与启示AI Agent的发展预测行业风险提示 前言 这是一篇关于Manus AI及其在通用人工智能领域的应用和前景的报告&#xff0c;主要介绍了Manus AI的产品定位、功能、技术架构、创始人经…

FPGA学习篇——Verilog学习3(关键字+注释方法+程序基本框架)

1 Verilog常用关键字 大概知道以下哪些是关键字就好&#xff0c;如何使用还是得在编写代码中来学习。 2 Verilog注释方法 Verilog有两种注释方式&#xff1a; 2.1 “ // ” 单行。 2.2 “ /* ... */ ” 可扩展多行。 3 Verilog程序基本框架 Verilog 的基本设计单元是“…

一文对比RAGFLOW和Open WebUI【使用场景参考】

一、RAGFLOW与Open WebUI RAGFLOW是一款基于深度文档理解构建的开源 RAG&#xff08;Retrieval-Augmented Generation&#xff09;引擎。RAGFlow 可以为各种规模的企业及个人提供一套精简的 RAG 工作流程&#xff0c;结合大语言模型&#xff08;LLM&#xff09;针对用户各类不…

SyntaxError: Missing semicolon

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…

游戏引擎学习第140天

回顾并为今天的内容做准备 目前代码的进展到了声音混音的部分。昨天我详细解释了声音的处理方式&#xff0c;声音在技术上是一个非常特别的存在&#xff0c;但在游戏中进行声音混音的需求其实相对简单明了&#xff0c;所以今天的任务应该不会太具挑战性。 今天我们会编写一个…

vue3如何配置环境和打包

很多新手友友们或刚从vue2切换到vue3的同学&#xff0c;对vue3不同环境配置和打包有很多困惑的地方&#xff0c;Jenna这就把vue3打包配置流程详细的写下来&#xff0c;你们只需要copy就好啦 1.创建环境文件 当我们把项目拿到手&#xff0c;只需要创建三个环境文件&#xff1a…

《AJAX:前端异步交互的魔法指南》

什么是AJAX AJAX&#xff08;Asynchronous JavaScript and XML&#xff0c;异步 JavaScript 和 XML&#xff09; 是一种用于创建异步网页应用的技术&#xff0c;允许网页在不重新加载整个页面的情况下&#xff0c;与服务器交换数据并局部更新页面内容。尽管名称中包含 XML&…

STM32-I2C通信协议

目录 一&#xff1a;什么是I2C通信协议 二&#xff1a;I2C通信 三&#xff1a;I2C时序图 四&#xff1a;面试常见问题 一&#xff1a;什么是I2C通信协议 I2C&#xff08;Inter-Integrated Circuit&#xff09;协议是一种串口通信协议&#xff0c;用于在集成电路之间传输数…

阿里推出全新推理模型(因果语言模型),仅1/20参数媲美DeepSeek R1

阿里Qwen 团队正式发布了他们最新的研究成果——QwQ-32B大语言模型&#xff01;这款模型不仅名字萌萌哒(QwQ)&#xff0c;实力更是不容小觑&#xff01;&#x1f60e; QwQ-32B 已在 Hugging Face 和 ModelScope 开源&#xff0c;采用了 Apache 2.0 开源协议。大家可通过 Qwen C…

GitCode 助力 vue3-element-admin:开启中后台管理前端开发新征程

源码仓库&#xff1a; https://gitcode.com/youlai/vue3-element-admin 后端仓库&#xff1a; https://gitcode.com/youlai/youlai-boot 开源助力&#xff0c;开启中后台快速开发之旅 vue3-element-admin 是一款精心打造的免费开源中后台管理前端模板&#xff0c;它紧密贴合…

接入DeepSeek,九牧开启AI卫浴新赛道!

2025年或可被称为AI新纪元元年&#xff0c;“具身智能”“智能机器人”“6G”等新词语出现在《政府工作报告》里&#xff0c;国家对制造业转型和“人工智能”的发展提出殷切期望。 近年来&#xff0c;围绕数智化&#xff0c;制造业开启了一场全球竞赛&#xff0c;在无人机、高…

概率、泛化与过拟合

1. 贝叶斯定理 (Bayes Rule) 贝叶斯公式&#xff0c;又称贝叶斯定理、贝叶斯法则&#xff0c;最初是用来描述两个事件的条件概率间的关系的公式&#xff0c;后来被人们发现具有很深刻的实际意义和应用价值。该公式的实际内涵是&#xff0c;支持某项属性的事件发生得愈多&#…

基于Python实现的智能旅游推荐系统(Django)

基于Python实现的智能旅游推荐系统(Django) 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat 系统功能实现 总体设计 系统实现 系统首页模块 统首页页面主要包括首页&#xff0c;旅游资讯&#xff0c;景点信息…

php代码审计工具-rips

代码审计 代码审计就是检查所写的代码中是否有漏洞&#xff0c;检查程序的源代码是否有权限从而被黑客攻击&#xff0c;同时也检查了书写的代码是否规范。通过自动化的审查和人工审查的方式&#xff0c;逐行检查源代码&#xff0c;发现源代码中安全缺陷所造成的漏洞&#xff0…

深入剖析分布式事务:原理、方案与实战指南

引言&#xff1a;为什么分布式事务成为架构师的必修课&#xff1f; 在微服务架构大行其道的今天&#xff0c;单体应用被拆分成多个独立服务。当一次业务操作需要跨多个服务/数据库完成时&#xff0c;传统数据库事务的ACID特性不再适用。订单创建需要同时操作订单服务和库存服务…

C语言100天练习题【记录本】

C语言经典100题&#xff08;手把手 编程&#xff09; 可以在哔哩哔哩找到&#xff08;url:C语言经典100题&#xff08;手把手 编程&#xff09;_哔哩哔哩_bilibili&#xff09; 已解决的天数&#xff1a;一&#xff0c;二&#xff0c;五&#xff0c;六&#xff0c;八&#xf…

计算机毕设-基于springboot的物业管理系统的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

Trae IDE新建C#工程

目录 1 结论 2 项目结构 3 项目代码 1 结论 新建C#工程来说&#xff0c;Trae的Chat比DeepSeek的Coder好用。 2 项目结构 MyWinFormsApp/ │ ├── Program.cs ├── Form1.cs ├── Form1.Designer.cs ├── MyResources/ │ └── MyResources.resx └── MyWin…

Linux 进程管理

一.进程 1.基本介绍 在Linux中每一个执行的程序都称之为进程&#xff0c;每一个进程都会分配一个进程号&#xff08;PID&#xff09;。进程以前台和后台两种方式存在&#xff0c;前台进程就是我们可以在屏幕上操作的&#xff0c;后台进程我们无法在屏幕上看到。 程序是静态的…