一.贪吃蛇
贪吃蛇是一款简单而富有乐趣的游戏,它的规则易于理解,但挑战性也很高。它已经成为经典的游戏之一,并且在不同的平台上一直受到人们的喜爱和回忆。
二.贪吃蛇的功能
-
游戏控制:玩家可以使用键盘输入设备来控制蛇的移动方向。
-
蛇的移动:蛇头会根据玩家的输入方向进行移动,而蛇身会随着蛇头的移动而延长,形成一条越来越长的蛇。
-
食物生成:游戏界面会随机生成食物,玩家控制的蛇头需要吃掉这些食物,每吃到一块食物,蛇的身体就会增长一节。
-
碰撞检测:游戏会检测蛇头是否与自身的身体或者墙壁碰撞,如果碰撞则游戏结束。
-
分数计算:游戏会记录玩家吃到的食物数量,并根据数量进行得分计算,通常吃到的食物越多,得分越高。
-
难度递增:随着蛇身越来越长,游戏的难度也会逐渐增加,因为玩家需要更小心地避免碰撞
-
双人游戏:贪吃蛇支持双人游玩,两位玩家可以相互竞争。
三.贪吃蛇项目的实现
1.游戏前的准备
1.1游戏的状态
enum state
枚举类型定义了游戏的状态,包括以下常量:ok
:正常状态,表示游戏进行中。by_wall
:撞墙状态,表示蛇撞到了地图的边界。by_body
:撞到蛇的身体状态,表示蛇头撞到了蛇身。by_self
:蛇咬到自己状态,表示蛇头咬到了自己的身体。by_end
:游戏结束状态。by_headpush
:蛇头相互碰撞状态,表示两个玩家的蛇头相互碰撞。
enum state
{ok = 1, // 游戏状态:正常状态by_wall, // 游戏状态:撞墙by_body, // 游戏状态:撞到蛇的身体by_self, // 游戏状态:蛇咬到自己by_end, // 游戏状态:游戏结束by_headpush // 游戏状态:蛇头相互碰撞
};
1.2蛇的移动方向
enum direction
枚举类型定义了蛇的移动方向,包括以下常量:up
:表示向上移动。down
:表示向下移动。left
:表示向左移动。right
:表示向右移动。
enum direction
{up = 1, // 方向:上down, // 方向:下left, // 方向:左right // 方向:右
};
1.3蛇的节点
struct SnakeNode
结构体用于表示蛇的一个节点,包括以下成员:x
:节点的横坐标。y
:节点的纵坐标。next
:指向下一个节点的指针。
typedef struct SnakeNode
{int x; // 蛇节点的横坐标int y; // 蛇节点的纵坐标struct SnakeNode* next; // 指向下一个蛇节点的指针
} SnakeNode, * pSnakeNode;
1.4食物的位置
struct Food
结构体用于表示食物的位置,包括以下成员:x
:食物的横坐标。y
:食物的纵坐标。
typedef struct Food
{int x; // 食物的横坐标int y; // 食物的纵坐标
} Food, * pFood;
1.5整个贪吃蛇游戏
struct Snake
结构体用于表示蛇,包括以下成员:_snake
:蛇的头节点指针。_food
:食物指针。_score
:蛇的得分。dir
:蛇的移动方向。sta
:当前游戏状态。_weight
:奖励。_sleeptime
:游戏循环每次暂停的时间间隔。
typedef struct Snake
{pSnakeNode _snake; // 蛇的头节点指针pFood _food; // 食物指针int _score; // 蛇的得分enum direction dir; // 蛇的移动方向enum state sta; // 当前游戏状态int _weight; // 奖励int _sleeptime; // 游戏循环每次暂停的时间间隔
} Snake, * pSnake;
2.游戏开始
2.1本地化设置
使用setlocale
函数设置本地化环境为当前系统默认的环境。因为环境的差异,导致我们的中文的宽字符无法被识别,所以要本地化设置。
int main()
{setlocale(LC_ALL, ""); // 设置本地化环境为当前系统默认的环境test(); // 调用test函数进行测试return 0; // 返回0表示程序正常结束
}
2.2 实现贪吃蛇的基本流程
test
函数中,通过srand
函数将当前时间作为随机数种子。然后使用do-while
循环进行游戏的测试和循环控制。在循环内部,首先使用malloc
动态分配了两个Snake
结构体的内存,并将其指针赋值给snake1
和snake2
。接着调用gamestart
函数开始游戏,传入snake1
和snake2
作为参数;然后调用gamerun
函数进行游戏运行,同样传入snake1
和snake2
作为参数;最后调用gameend
函数结束游戏并释放内存,同样传入snake1
和snake2
作为参数。之后,使用system("cls")
清空控制台屏幕,然后在指定位置打印提示信息。接下来,使用getchar
函数获取一个字符并赋值给变量ch
,再使用getchar
读取多余的换行符。最后,判断ch
是否为字符'y'
或'Y'
,如果是则继续进行下一轮游戏。
#include "snake.h" // 引入自定义的头文件snake.hvoid test()
{srand((unsigned int)time(NULL)); // 使用当前时间作为随机数种子int ch;do {pSnake snake1 = (pSnake)malloc(sizeof(Snake)); // 动态分配一个Snake结构体的内存,并将其指针赋给snake1pSnake snake2 = (pSnake)malloc(sizeof(Snake)); // 动态分配一个Snake结构体的内存,并将其指针赋给snake2gamestart(snake1, snake2); // 调用gamestart函数开始游戏,传入snake1和snake2作为参数gamerun(snake1, snake2); // 调用gamerun函数进行游戏运行,传入snake1和snake2作为参数gameend(snake1, snake2); // 调用gameend函数结束游戏,释放内存,传入snake1和snake2作为参数system("cls"); // 清空控制台屏幕setpos(46, 15); // 设置光标位置为(46, 15)printf("选择Y/N");setpos(50, 16); // 设置光标位置为(50, 16)ch = getchar(); // 从标准输入中获取一个字符,赋值给chgetchar(); // 读取多余的换行符} while (ch == 'y' || ch == 'Y'); // 如果输入的字符是'y'或'Y',则继续进行下一轮游戏}
2.3设置光标
该函数接受两个整型参数x
和y
,分别表示光标的横坐标和纵坐标。在函数内部,首先调用GetStdHandle
函数获取标准输出句柄,该句柄用于操作控制台窗口。然后创建一个COORD
结构体变量coord
,并将x
赋值给coord.X
,将y
赋值给coord.Y
,即设置光标的位置。最后,调用SetConsoleCursorPosition
函数将控制台光标位置设置为指定的坐标。这样,在调用setpos
函数时,控制台光标会移动到指定的位置。
void setpos(int x, int y)
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄COORD coord;coord.X = x; // 设置光标的横坐标为xcoord.Y = y; // 设置光标的纵坐标为ySetConsoleCursorPosition(handle, coord); // 设置控制台光标位置为指定的坐标
}
2.4游戏的初始化
该函数接受两个参数,snake1
和snake2
,分别表示两条蛇的指针。函数开始使用断言assert
,确保snake1
和snake2
不为NULL
,以保证后续操作的正确性。然后依次调用以下函数:
welcome
:显示游戏欢迎信息,可能是在控制台输出一些欢迎文本。creatmap
:创建游戏地图,可能是在控制台上绘制一个游戏地图的界面。initsnake
:初始化蛇的身体,可能是将蛇的初始位置和长度设置为默认值。creatsnake
:生成两条蛇的初始位置,可能是将两条蛇放置在地图的指定位置。creatfood
:生成食物的位置,以供两条蛇争夺,可能是将食物随机放置在地图的空闲位置。gameinfo
:显示游戏信息,可能是在控制台上显示当前游戏的状态、得分等信息。
void gamestart(pSnake snake1, pSnake snake2)
{assert(snake1 && snake2); // 使用断言确保snake1和snake2不为NULLwelcome(); // 调用welcome函数,显示游戏欢迎信息creatmap(); // 调用creatmap函数,创建游戏地图initsnake(snake1); // 调用initsnake函数,初始化snake1蛇身initsnake(snake2); // 调用initsnake函数,初始化snake2蛇身creatsnake(snake1, snake2);// 调用creatsnake函数,生成snake1和snake2的初始位置creatfood(snake1, snake2);// 调用creatfood函数,生成食物的位置,以供snake1和snake2争夺creatfood(snake2, snake1);// 调用creatfood函数,生成食物的位置,以供snake2和snake1争夺gameinfo(snake1, snake2); // 调用gameinfo函数,显示游戏信息
}
2.3.1欢迎信息
该函数没有参数。在函数内部,首先调用system
函数设置控制台窗口的大小为100列,30行,使用命令mode con cols=100 lines=30
实现。然后调用system
函数设置控制台窗口的标题为"贪吃蛇",使用命令title 贪吃蛇
实现。接下来,获取标准输出句柄,并获取控制台光标信息。将光标的可见性设置为false
,即不可见,然后再将修改后的光标信息设置回控制台。然后使用setpos
函数设置光标位置为(40, 10),在该位置打印欢迎信息。接着,使用setpos
函数设置光标位置为(40, 20),调用system("pause")
暂停程序的执行,等待用户按下任意键。然后使用system("cls")
清空控制台屏幕。接下来,使用setpos
函数设置光标位置为(38, 10),然后依次打印游戏规则的内容。再次使用setpos
函数设置光标位置为(40, 20),使用system("pause")
暂停程序的执行,等待用户按下任意键。最后,使用system("cls")
清空控制台屏幕。
void welcome()
{system("mode con cols=100 lines=30"); // 设置控制台窗口的大小为100列,30行system("title 贪吃蛇"); // 设置控制台窗口的标题为"贪吃蛇"HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄CONSOLE_CURSOR_INFO cursor;GetConsoleCursorInfo(handle, &cursor); // 获取控制台光标信息cursor.bVisible = false; // 将光标设置为不可见SetConsoleCursorInfo(handle, &cursor); // 设置控制台光标信息setpos(40, 10); // 设置光标位置为(40, 10)printf("欢迎来到贪吃蛇游戏!");setpos(40, 20); // 设置光标位置为(40, 20)system("pause"); // 暂停程序的执行,等待用户按下任意键system("cls"); // 清空控制台屏幕setpos(38, 10); // 设置光标位置为(38, 10)printf("游戏规则如下:\n");setpos(38, 11); // 设置光标位置为(38, 11)printf("1.用← → ↑ ↓或a d w s进行操作");setpos(38, 12); // 设置光标位置为(38, 12)printf("2.用f1进行加速,用进行f2减速");setpos(38, 13); // 设置光标位置为(38, 13)printf("3.空格键暂停游戏");setpos(38, 14); // 设置光标位置为(38, 14)printf("4.esc退出游戏");setpos(40, 20); // 设置光标位置为(40, 20)system("pause"); // 暂停程序的执行,等待用户按下任意键system("cls"); // 清空控制台屏幕
}
2.3.2创建地图
该函数没有参数。在函数内部,首先使用setpos
函数将光标位置设置为(0, 0)。然后使用循环,每次递增2,打印墙的字符。接着,使用setpos
函数将光标位置设置为(0, 25),再次使用循环,每次递增2,打印墙的字符。然后使用循环,从1到24,每次递增1,使用setpos
函数将光标位置设置为(0, i),打印墙的字符。最后,使用循环,从1到24,每次递增1,使用setpos
函数将光标位置设置为(56, i),打印墙的字符。这样,通过在控制台上打印一系列墙的字符,创建了游戏地图的界面。
void creatmap()
{setpos(0, 0);for (int i = 0; i < 58; i += 2){wprintf(L"%c", Wall);}setpos(0, 25);for (int i = 0; i < 58; i += 2){wprintf(L"%c", Wall);}for (int i = 1; i < 25; i++){setpos(0, i);wprintf(L"%c", Wall);}for (int i = 1; i < 25; i++){setpos(56, i);wprintf(L"%c", Wall);}
}
2.3.3初始化整个贪吃蛇游戏
该函数接受一个指向Snake
结构体的指针snake
作为参数。在函数内部,将蛇的链表指针_snake
和食物的指针_food
都设置为NULL,表示蛇链表和食物尚未创建。将分数_score
初始化为0,表示初始分数为0。将蛇的初始移动方向dir
设置为left
,表示初始方向为向左移动。将蛇的状态sta
设置为ok
,表示蛇的初始状态为正常状态。将蛇的初始长度_weight
设置为10,表示蛇的初始长度为10。将蛇的初始移动速度_sleeptime
设置为200毫秒,表示蛇的初始移动速度为每200毫秒移动一次。通过这些初始化操作,为蛇游戏的相关数据设置了初始值,以便游戏开始时使用。
void initsnake(pSnake snake)
{snake->_snake = NULL; // 将蛇的链表指针设置为NULLsnake->_food = NULL; // 将食物的指针设置为NULLsnake->_score = 0; // 将分数初始化为0snake->dir = left; // 将蛇的初始移动方向设置为左侧snake->sta = ok; // 将蛇的初始状态设置为正常状态snake->_weight = 10; // 将蛇的初始长度设置为10snake->_sleeptime = 200; // 将蛇的初始移动速度设置为200毫秒
}
2.3.4创建蛇的初始状态
该函数接受两个指向Snake
结构体的指针snake1
和snake2
作为参数。在函数内部,首先创建蛇1的头结点p1
,并将其位置设置为(24, 5)。然后将蛇1的链表指针_snake
指向头结点p1
,表示蛇1的链表的起始节点为头结点。然后在控制台上打印头结点的字符表示。接着使用循环,从1到4,每次递增1,创建蛇1的身体节点j
,并将其位置设置为(24 + i * 2, 5)。然后在控制台上打印身体节点的字符表示。将头结点的next
指针指向身体节点j
,表示蛇1的头结点连接到身体节点。然后更新头结点为身体节点,为下一个身体节点的创建做准备。接下来,按照类似的方式,创建蛇2的头结点和身体节点。蛇2的头结点位置为(24, 22),蛇2的身体节点位置从(26, 22)开始递增。在控制台上打印蛇2的头结点和身体节点的字符表示。通过这些操作,创建了两条蛇的初始状态,每条蛇由一个头结点和四个身体节点组成。
void creatsnake(pSnake snake1, pSnake snake2)
{pSnakeNode p1 = (pSnakeNode)malloc(sizeof(SnakeNode)); // 创建蛇1的头结点assert(p1);p1->next = NULL;p1->x = 24;p1->y = 5;snake1->_snake = p1;setpos(p1->x, p1->y);wprintf(L"%c", Body);for (int i = 1; i < 5; i++){pSnakeNode j = (pSnakeNode)malloc(sizeof(SnakeNode)); // 创建蛇1的身体节点assert(j);j->x = 24 + i * 2;j->y = 5;j->next = NULL;setpos(j->x, j->y);wprintf(L"%c", Body);p1->next = j;p1 = p1->next;}pSnakeNode p2 = (pSnakeNode)malloc(sizeof(SnakeNode)); // 创建蛇2的头结点assert(p2);p2->next = NULL;p2->x = 24;p2->y = 22;snake2->_snake = p2;setpos(p2->x, p2->y);wprintf(L"%c", Body);for (int i = 1; i < 5; i++){pSnakeNode j = (pSnakeNode)malloc(sizeof(SnakeNode)); // 创建蛇2的身体节点assert(j);j->x = 24 + i * 2;j->y = 22;j->next = NULL;setpos(j->x, j->y);wprintf(L"%c", Body);p2->next = j;p2 = p2->next;}
}
2.3.5创建食物
该函数接受两个指向Snake
结构体的指针snake1
和snake2
作为参数。在函数内部,首先定义变量cout
用于控制是否需要重新生成食物位置。然后定义变量x
和y
用于存储食物的坐标。通过循环和条件判断,生成在游戏区域内的随机坐标(x, y)
,其中x
是2到53之间的偶数,y
是1到24之间的任意数。这样可以保证食物落在游戏区域内的空闲位置。接着,遍历蛇1和蛇2的身体节点,以及蛇2的食物位置(如果存在),检查食物位置是否与这些节点重叠。如果有重叠,则重新生成食物位置,通过goto
语句返回到again
标签处重新生成。如果食物位置与蛇的身体节点和食物位置都不重叠,将变量cout
设置为1,表示食物位置已经确定。然后,将食物的坐标存储到蛇1的数据结构中,将食物指针指向食物结构体。在控制台上打印食物的字符表示,以显示食物的位置。通过这些操作,生成并放置了食物,并确保食物不与蛇的身体节点和其他食物重叠。
void creatfood(pSnake snake1, pSnake snake2)
{int cout = 0;int x, y;pFood food1 = (pFood)malloc(sizeof(Food)); // 创建食物结构体assert(food1);
again:if (cout == 0){do {x = rand() % 52 + 2; // 生成一个2到53之间的随机数作为x坐标y = rand() % 23 + 1; // 生成一个1到24之间的随机数作为y坐标} while (x % 2 != 0); // 保证x坐标是偶数,以确保食物落在游戏区域内的空闲位置pSnakeNode p1 = snake1->_snake;pSnakeNode p2 = snake1->_snake;while (p1 && p2){if (snake2->_food == NULL){if (p1->x == x && p1->y == y && p2->x == x && p2->y == y)goto again; // 如果食物位置与蛇1和蛇2的身体节点位置重叠,则重新生成食物位置}else if (p1->x == x && p1->y == y && p2->x == x && p2->y == y && snake2->_food->x == x && snake2->_food->y == y)goto again; // 如果食物位置与蛇1、蛇2的身体节点位置以及蛇2的食物位置重叠,则重新生成食物位置p1 = p1->next;p2 = p2->next;}cout = 1;}food1->x = x;food1->y = y;snake1->_food = food1; // 将食物指针存储到蛇1的数据结构中setpos(food1->x, food1->y);wprintf(L"%c", FOOD); // 在控制台上打印食物的字符表示
}
2.3.6显示游戏的规则
该函数接受两个指向Snake
结构体的指针snake1
和snake2
作为参数。在函数内部,使用setpos
函数将光标定位到相应的位置,然后使用printf
函数将相关信息打印在控制台上。首先打印蛇1的分数和奖励信息,分别位于(70, 6)和(70, 7)的位置。接着打印蛇2的分数和奖励信息,分别位于(70, 10)和(70, 11)的位置。然后打印游戏规则,分别位于(62, 15)到(62, 20)的位置。通过这些操作,在控制台上显示了游戏的相关信息和规则。
void gameinfo(pSnake snake1, pSnake snake2)
{setpos(70, 6);printf("分数:%d", snake1->_score); // 打印蛇1的分数setpos(70, 7);printf("奖励:%d", snake1->_weight); // 打印蛇1的奖励setpos(70, 10);printf("分数:%d", snake2->_score); // 打印蛇2的分数setpos(70, 11);printf("奖励:%d", snake2->_weight); // 打印蛇2的奖励setpos(62, 15);printf("游戏规则如下:");setpos(62, 16);printf("1.用← → ↑ ↓或a d w s进行操作");setpos(62, 17);printf("2.用f1进行加速,用进行f2减速");setpos(62, 18);printf("3.空格键暂停游戏");setpos(62, 19);printf("4.esc退出游戏");setpos(62, 20);printf("5.咬到蛇身或撞墙都会死");
}
3.游戏进行中
3.1控制游戏的运行逻辑
该函数接受两个指向Snake
结构体的指针snake1
和snake2
作为参数。在函数内部,使用do-while
循环来控制游戏的逻辑。在循环内部,根据按键的状态来判断玩家的操作,例如W、S、A、D键用于控制蛇1的移动方向,空格键用于暂停游戏,ESC键用于结束游戏。同时,F1和F2键用于加速和减速蛇的移动速度,上下左右箭头键用于控制蛇2的移动方向。接着调用snakemove
函数来移动蛇1和蛇2的位置。然后更新显示蛇1和蛇2的分数和奖励信息。最后根据蛇1的睡眠时间进行延时,然后继续下一轮循环,直到蛇1或蛇2的状态不再为正常(游戏结束)。
void gamerun(pSnake snake1, pSnake snake2)
{do {if (Key_state(0x57) && snake1->dir != down){snake1->dir = up; // 如果按下W键且蛇1不朝下移动,则将蛇1的方向设置为上}else if (Key_state(0x53) && snake1->dir != up){snake1->dir = down; // 如果按下S键且蛇1不朝上移动,则将蛇1的方向设置为下}else if (Key_state(0x41) && snake1->dir != right){snake1->dir = left; // 如果按下A键且蛇1不朝右移动,则将蛇1的方向设置为左}else if (Key_state(0x44) && snake1->dir != left){snake1->dir = right; // 如果按下D键且蛇1不朝左移动,则将蛇1的方向设置为右}else if (Key_state(VK_SPACE)){pause(); // 如果按下空格键,则暂停游戏}else if (Key_state(VK_ESCAPE)){snake1->sta = by_end; // 如果按下ESC键,则将蛇1的状态设置为结束snake2->sta = by_end; // 将蛇2的状态设置为结束}else if (Key_state(VK_F1)){if (snake1->_sleeptime >= 80){(snake1->_sleeptime) -= 20; // 如果按下F1键且蛇1的睡眠时间大于等于80,则减少蛇1的睡眠时间(snake1->_weight) += 2; // 增加蛇1的奖励}setpos(70, 7);printf("奖励:%d", snake1->_weight); // 更新蛇1的奖励显示if (snake2->_sleeptime >= 80){(snake2->_sleeptime) -= 20; // 如果按下F1键且蛇2的睡眠时间大于等于80,则减少蛇2的睡眠时间(snake2->_weight) += 2; // 增加蛇2的奖励}setpos(70, 11);printf("奖励:%d", snake2->_weight); // 更新蛇2的奖励显示}else if (Key_state(VK_F2)){if (snake1->_sleeptime <= 280){(snake1->_sleeptime) += 20; // 如果按下F2键且蛇1的睡眠时间小于等于280,则增加蛇1的睡眠时间(snake1->_weight) -= 2; // 减少蛇1的奖励}setpos(70, 7);printf("奖励:%d", snake1->_weight); // 更新蛇1的奖励显示if (snake2->_sleeptime <= 280){(snake2->_sleeptime) += 20; // 如果按下F2键且蛇2的睡眠时间小于等于280,则增加蛇2的睡眠时间(snake2->_weight) -= 2; // 减少蛇2的奖励}setpos(70, 11);printf("奖励:%d", snake2->_weight); // 更新蛇2的奖励显示}else if (Key_state(VK_UP) && snake2->dir != down){snake2->dir = up; // 如果按下上箭头键且蛇2不朝下移动,则将蛇2的方向设置为上}else if (Key_state(VK_DOWN) && snake2续:.dir != up){snake2->dir = down; // 如果按下下箭头键且蛇2不朝上移动,则将蛇2的方向设置为下}else if (Key_state(VK_LEFT) && snake2->dir != right){snake2->dir = left; // 如果按下左箭头键且蛇2不朝右移动,则将蛇2的方向设置为左}else if (Key_state(VK_RIGHT) && snake2->dir != left){snake2->dir = right; // 如果按下右箭头键且蛇2不朝左移动,则将蛇2的方向设置为右}snakemove(snake1, snake2); // 移动蛇1snakemove(snake2, snake1); // 移动蛇2setpos(70, 6);printf("分数:%d", snake1->_score); // 更新蛇1的分数显示setpos(70, 7);printf("奖励:%d", snake1->_weight); // 更新蛇1的奖励显示setpos(70, 10);printf("分数:%d", snake2->_score); // 更新蛇2的分数显示setpos(70, 11);printf("奖励:%d", snake2->_weight); // 更新蛇2的奖励显示Sleep(snake1->_sleeptime); // 根据蛇1的睡眠时间进行延时} while (snake1->sta == ok && snake2->sta == ok); // 当蛇1和蛇2的状态都为正常时继续循环
}
3.2 游戏的暂停
在函数内部,使用一个无限循环while (1)
来检测空格键的状态。如果空格键被按下(Key_state(VK_SPACE)
返回true),则函数使用return
语句立即返回,从而退出循环并继续游戏的执行。如果空格键未被按下,函数会通过Sleep(1000)
函数让程序休眠1秒钟,然后再次检测空格键的状态。这样循环会一直执行,直到空格键被按下并函数返回。这样就实现了游戏的暂停功能。
void pause()
{while (1){if (Key_state(VK_SPACE))return;Sleep(1000);}
}
3.3 蛇的移动及一系列的判断
该函数包含了以下功能:
- 创建一个新的蛇头节点
i
,并将其插入到蛇身链表的头部。 - 根据贪吃蛇的方向更新蛇头的位置。
- 如果贪吃蛇吃到了食物,增加贪吃蛇的得分并生成新的食物。
- 如果贪吃蛇没有吃到食物,移动蛇尾,即删除蛇身链表的最后一个节点,并在屏幕上擦除该节点的位置。
- 检查贪吃蛇是否撞墙,如果是则设置状态为
by_wall
并返回。 - 检查贪吃蛇是否自噬,即蛇头是否碰到了蛇身的其他部分。
- 绘制贪吃蛇的身体,即在屏幕上打印出每个节点的位置。
void snakemove(pSnake snake1, pSnake snake2)
{pSnakeNode i = (pSnakeNode)malloc(sizeof(SnakeNode));assert(i);i->next = snake1->_snake;// 根据贪吃蛇的方向更新蛇头的位置if (snake1->dir == up){i->x = snake1->_snake->x;i->y = snake1->_snake->y - 1;}else if (snake1->dir == down){i->x = snake1->_snake->x;i->y = snake1->_snake->y + 1;}else if (snake1->dir == left){i->x = snake1->_snake->x - 2;i->y = snake1->_snake->y;}else if (snake1->dir == right){i->x = snake1->_snake->x + 2;i->y = snake1->_snake->y;}else if (snake1->sta == by_end){return;}// 更新蛇头的位置snake1->_snake = i;pSnakeNode p = snake1->_snake;// 判断是否吃到了食物if ((i->x == snake1->_food->x && i->y == snake1->_food->y) || (i->x == snake2->_food->x && i->y == snake2->_food->y)){(snake1->_score) += snake1->_weight;creatfood(snake1, snake2);}else{// 如果没有吃到食物,则移动蛇尾pSnakeNode pre = NULL;while (p->next != NULL){pre = p;p = p->next;}pre->next = NULL;setpos(p->x, p->y);printf(" ");free(p);}// 判断是否撞墙if (snake1->_snake->x == 0 || snake1->_snake->x == 56 || snake1->_snake->y == 0 || snake1->_snake->y == 25){snake1->sta = by_wall;return;}// 判断是否自噬kill_self(snake1, snake2);// 绘制蛇的身体p = i;while (p){setpos(p->x, p->y);wprintf(L"%c", Body);p = p->next;}
}
3.3.1 判断贪吃蛇是否自噬或者与另一个贪吃相撞
该函数包含了以下功能:
- 初始化两个指针
p1
和p2
分别指向贪吃蛇1和贪吃蛇2的蛇身链表的第二个节点(跳过蛇头)。 - 检测贪吃蛇1的蛇头是否与贪吃蛇2的蛇头位置重叠,如果是,则将两个贪吃蛇的状态都设置为
by_headpush
表示头部碰撞,并立即返回。 - 遍历贪吃蛇1和贪吃蛇2的蛇身链表,检测贪吃蛇1的蛇头是否与贪吃蛇2的蛇身节点或贪吃蛇1自身的蛇身节点位置重叠,如果是,则将贪吃蛇1的状态设置为相应的自噬状态(
by_body
或by_self
),并立即返回。
这个函数的作用是在每次贪吃蛇移动后检测是否发生自噬或者与其他贪吃蛇相撞的情况,以便在后续的游戏逻辑中处理相应的情况。
void kill_self(pSnake snake1, pSnake snake2)
{pSnakeNode p1 = snake1->_snake->next;pSnakeNode p2 = snake2->_snake->next;// 检测蛇头是否碰撞if (snake1->_snake->x == snake2->_snake->x && snake1->_snake->y == snake2->_snake->y){snake1->sta = by_headpush;snake2->sta = by_headpush;return;}// 检测蛇头是否与蛇身碰撞while (p1 && p2){if (snake1->_snake->x == p2->x && snake1->_snake->y == p2->y){snake1->sta = by_body;return;}if (snake1->_snake->x == p1->x && snake1->_snake->y == p1->y){snake1->sta = by_self;return;}p1 = p1->next;p2 = p2->next;}
}
4.游戏结束
该函数包含以下功能:
- 检查贪吃蛇的状态,根据不同的情况显示相应的游戏结束提示信息。
- 释放贪吃蛇1和贪吃蛇2所占用的内存。
具体逻辑如下:
首先,函数检查两条蛇的状态是否相同。如果状态不同,根据不同的状态显示相应的游戏结束提示信息。
- 如果蛇1的状态是
by_body
,表示蛇1咬到了蛇2的身体,显示"玩家1咬到对方蛇身了,游戏结束!"的提示信息。 - 如果蛇1的状态是
by_wall
,表示蛇1撞到了墙壁,显示"玩家1撞到墙了,游戏结束!"的提示信息。 - 如果蛇2的状态是
by_body
,表示蛇2咬到了蛇1的身体,显示"玩家2咬到对方蛇身了,游戏结束!"的提示信息。 - 如果蛇2的状态是
by_wall
,表示蛇2撞到了墙壁,显示"玩家2撞到墙了,游戏结束!"的提示信息。 - 如果蛇1的状态是
by_self
,表示蛇1咬到了自己的身体,显示"玩家1咬到自己蛇身了,游戏结束!"的提示信息。 - 如果蛇2的状态是
by_self
,表示蛇2咬到了自己的身体,显示"玩家2咬到自己蛇身了,游戏结束!"的提示信息。
如果两条蛇的状态相同,继续检查状态。
- 如果蛇1的状态是
by_end
,表示蛇1到达了游戏结束状态,显示"游戏结束!"的提示信息。 - 如果蛇1的状态是
by_body
,表示蛇1和蛇2都咬到了对方的身体,显示"玩家1和玩家2都咬到对方蛇身了,游戏结束!"的提示信息。 - 如果蛇1的状态是
by_wall
,表示蛇1和蛇2都撞到了墙壁,显示"玩家1和玩家2撞到墙了,游戏结束!"的提示信息。 - 如果蛇1的状态是
by_headpush
,表示蛇1和蛇2相互碰撞,显示"两位玩家相撞了,游戏结束!"的提示信息。 - 如果蛇1的状态是
by_self
,表示蛇1和蛇2都咬到了自己的身体,显示"玩家1和玩家2都咬到自己蛇身了,游戏结束!"的提示信息。
接下来,函数释放贪吃蛇1所占用的内存。首先释放蛇1的食物(_food
),然后依次释放蛇1的节点(_snake
)所占用的内存。最后,函数释放贪吃蛇2所占用的内存。首先释放蛇2的食物(_food
),然后依次释放蛇2的节点(_snake
)所占用的内存。
void gameend(pSnake snake1, pSnake snake2)
{// 检查贪吃蛇状态,显示相应的游戏结束提示信息并释放内存if (snake1->sta != snake2->sta){if (snake1->sta == by_body){system("cls");setpos(42, 15);printf("玩家1咬到对方蛇身了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_wall){system("cls");setpos(38, 15);printf("玩家1撞到墙了,游戏结束!");setpos(40, 22);system("pause");}else if (snake2->sta == by_body){system("cls");setpos(34, 15);printf("玩家2咬到对方蛇身了,游戏结束!");setpos(40, 22);system("pause");}else if (snake2->sta == by_wall){system("cls");setpos(34, 15);printf("玩家2撞到墙了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_self){system("cls");setpos(34, 15);printf("玩家1咬到自己蛇身了,游戏结束!");setpos(40, 22);system("pause");}else if (snake2->sta == by_self){system("cls");setpos(34, 15);printf("玩家2咬到自己蛇身了,游戏结束!");setpos(40, 22);system("pause");}}else{if (snake1->sta == by_end){system("cls");setpos(48, 15);printf("游戏结束!");setpos(42, 22);system("pause");}else if (snake1->sta == by_body){system("cls");setpos(34, 15);printf("玩家1和玩家2都咬到对方蛇身了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_wall){system("cls");setpos(34, 15);printf("玩家1和玩家2撞到墙了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_headpush){system("cls");setpos(34, 15);printf("两位玩家相撞了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_self){system("cls");setpos(34, 15);printf("玩家1和玩家2都咬到自己蛇身了,游戏结束!");setpos(40, 22);system("pause");}}// 释放贪吃蛇1的内存free(snake1->_food);pSnakeNode p = snake1->_snake;pSnakeNode pre = NULL;while (p){pre = p;p = p->next;pre->next = NULL;free(pre);}free(snake1);// 释放贪吃蛇2的内存free(snake2->_food);p = snake2->_snake;pre = NULL;while (p){pre = p;p = p->next;pre->next = NULL;free(pre);}free(snake2);
}
四.贪吃蛇的测试
五.贪吃蛇的源码呈现
1.snake.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<locale.h>
#include<stdbool.h>
#include<assert.h>
#include<time.h>
#define Wall L'□'
#define Body L'●'
#define FOOD L'★'
#define Key_state(KEY) ((GetAsyncKeyState(KEY)&(0x1))?1:0)
enum state
{ok = 1,by_wall,by_body,by_self,by_end,by_headpush
};
enum direction
{up = 1,down,left,right
};
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
typedef struct Food
{int x;int y;
}Food, * pFood;
typedef struct Snake
{pSnakeNode _snake;pFood _food;int _score;enum direction dir;enum state sta;int _weight;int _sleeptime;
}Snake, * pSnake;
void gamestart(pSnake snake1,pSnake snake2);
void setpos(int x, int y);
void welcome();
void creatmap();
void creatfood(pSnake snake1,pSnake snake2);
void initsnake(pSnake snake1);
void creatsnake(pSnake snake1, pSnake snake2);
void gameinfo(pSnake snake1, pSnake snake2);
void gamerun(pSnake snake1, pSnake snake2);
void pause();
void snakemove(pSnake snake1,pSnake snake2);
void kill_self(pSnake snake);
void gameend(pSnake snake);
2.test.c
#include"snake.h"
void test()
{srand((unsigned int)time(NULL));int ch;do {pSnake snake1 = (pSnake)malloc(sizeof(Snake));pSnake snake2 = (pSnake)malloc(sizeof(Snake));gamestart(snake1,snake2);gamerun(snake1,snake2);gameend(snake1,snake2);system("cls");setpos(46, 15);printf("选择Y/N");setpos(50, 16);ch = getchar();getchar();} while (ch == 'y' || ch == 'Y');
}
int main()
{setlocale(LC_ALL, "");test();return 0;
}
3.snake.c
#include"snake.h"
void setpos(int x, int y)
{HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);COORD coord;coord.X = x;coord.Y = y;SetConsoleCursorPosition(handle, coord);
}
void welcome()
{system("mode con cols=100 lines=30");system("title 贪吃蛇");HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO cursor;GetConsoleCursorInfo(handle, &cursor);cursor.bVisible = false;SetConsoleCursorInfo(handle, &cursor);setpos(40, 10);printf("欢迎来到贪吃蛇游戏!");setpos(40, 20);system("pause");system("cls");setpos(38, 10);printf("游戏规则如下:\n");setpos(38, 11);printf("1.用← → ↑ ↓或a d w s进行操作");setpos(38, 12);printf("2.用f1进行加速,用进行f2减速");setpos(38, 13);printf("3.空格键暂停游戏");setpos(38, 14);printf("4.esc退出游戏");setpos(40, 20);system("pause");system("cls");
}
void creatmap()
{setpos(0, 0);for (int i = 0; i < 58; i += 2){wprintf(L"%c", Wall);}setpos(0, 25);for (int i = 0; i < 58; i += 2){wprintf(L"%c", Wall);}for (int i = 1; i < 25; i++){setpos(0, i);wprintf(L"%c", Wall);}for (int i = 1; i < 25; i++){setpos(56, i);wprintf(L"%c", Wall);}
}
void initsnake(pSnake snake)
{snake->_snake = NULL;snake->_food = NULL;snake->_score = 0;snake->dir = left;snake->sta = ok;snake->_weight = 10;snake->_sleeptime = 200;
}
void creatsnake(pSnake snake1,pSnake snake2)
{pSnakeNode p1 = (pSnakeNode)malloc(sizeof(SnakeNode));assert(p1);p1->next = NULL;p1->x = 24;p1->y = 5;snake1->_snake = p1;setpos(p1->x, p1->y);wprintf(L"%c", Body);for (int i = 1; i < 5; i++){pSnakeNode j = (pSnakeNode)malloc(sizeof(SnakeNode));assert(j);j->x = 24 + i * 2;j->y = 5;j->next = NULL;setpos(j->x, j->y);wprintf(L"%c", Body);p1->next = j;p1 = p1->next;}pSnakeNode p2 = (pSnakeNode)malloc(sizeof(SnakeNode));assert(p2);p2->next = NULL;p2->x = 24;p2->y = 22;snake2->_snake = p2;setpos(p2->x, p2->y);wprintf(L"%c", Body);for (int i = 1; i < 5; i++){pSnakeNode j = (pSnakeNode)malloc(sizeof(SnakeNode));assert(j);j->x = 24 + i * 2;j->y = 22;j->next = NULL;setpos(j->x, j->y);wprintf(L"%c", Body);p2->next = j;p2 = p2->next;}
}
void creatfood(pSnake snake1,pSnake snake2)
{int cout = 0;int x, y;pFood food1 = (pFood)malloc(sizeof(Food));assert(food1);
again:if (cout == 0){do {x = rand() % 52 + 2;y = rand() % 23 + 1;} while (x % 2 != 0);pSnakeNode p1 = snake1->_snake;pSnakeNode p2 = snake1->_snake;while (p1&&p2){if (snake2->_food == NULL){if (p1->x == x && p1->y == y && p2->x == x && p2->y == y)goto again;}else if (p1->x == x && p1->y == y && p2->x == x && p2->y == y&&snake2->_food->x==x&&snake2->_food->y==y)goto again;p1 = p1->next;p2 = p2->next;}cout = 1;}food1->x = x;food1->y = y;snake1->_food = food1;setpos(food1->x, food1->y);wprintf(L"%c", FOOD);
}
void gameinfo(pSnake snake1,pSnake snake2)
{setpos(70, 6);printf("分数:%d", snake1->_score);setpos(70, 7);printf("奖励:%d", snake1->_weight);setpos(70, 10);printf("分数:%d", snake2->_score);setpos(70, 11);printf("奖励:%d", snake2->_weight);setpos(62, 15);printf("游戏规则如下:");setpos(62, 16);printf("1.用← → ↑ ↓或a d w s进行操作");setpos(62, 17);printf("2.用f1进行加速,用进行f2减速");setpos(62, 18);printf("3.空格键暂停游戏");setpos(62, 19);printf("4.esc退出游戏");setpos(62, 20);printf("5.咬到蛇身或撞墙都会死");
}
void pause()
{while (1){if (Key_state(VK_SPACE))return;Sleep(1000);}
}
void kill_self(pSnake snake1,pSnake snake2)
{pSnakeNode p1 = snake1->_snake->next;pSnakeNode p2 = snake2->_snake->next;if (snake1->_snake->x == snake2->_snake->x && snake1->_snake->y == snake2->_snake->y){snake1->sta = by_headpush;snake2->sta = by_headpush;return;}while (p1&&p2){if (snake1->_snake->x == p2->x && snake1->_snake->y == p2->y){snake1->sta = by_body;return;}if (snake1->_snake->x == p1->x && snake1->_snake->y == p1->y){snake1->sta = by_self;return;}p1 = p1->next;p2 = p2->next;}
}
void snakemove(pSnake snake1,pSnake snake2)
{pSnakeNode i = (pSnakeNode)malloc(sizeof(SnakeNode));assert(i);i->next = snake1->_snake;if (snake1->dir == up){i->x = snake1->_snake->x;i->y = snake1->_snake->y - 1;}else if (snake1->dir == down){i->x = snake1->_snake->x;i->y = snake1->_snake->y + 1;}else if (snake1->dir == left){i->x = snake1->_snake->x - 2;i->y = snake1->_snake->y;}else if (snake1->dir == right){i->x = snake1->_snake->x + 2;i->y = snake1->_snake->y;}else if (snake1->sta == by_end){return;}snake1->_snake = i;pSnakeNode p = snake1->_snake;if ((i->x == snake1->_food->x && i->y == snake1->_food->y)|| (i->x == snake2->_food->x && i->y == snake2->_food->y)){(snake1->_score) += snake1->_weight;creatfood(snake1,snake2);}else{pSnakeNode pre = NULL;while (p->next != NULL){pre = p;p = p->next;}pre->next = NULL;setpos(p->x, p->y);printf(" ");free(p);}if (snake1->_snake->x == 0 || snake1->_snake->x == 56 || snake1->_snake->y == 0 || snake1->_snake->y == 25){snake1->sta = by_wall;return;}kill_self(snake1,snake2);p = i;while (p){setpos(p->x, p->y);wprintf(L"%c", Body);p = p->next;}
}
void gamestart(pSnake snake1,pSnake snake2)
{assert(snake1&&snake2);welcome();creatmap();initsnake(snake1);initsnake(snake2);creatsnake(snake1,snake2);creatfood(snake1,snake2);creatfood(snake2, snake1);gameinfo(snake1,snake2);
}
void gamerun(pSnake snake1,pSnake snake2)
{do {if (Key_state(0x57) && snake1->dir != down){snake1->dir = up;}else if (Key_state(0x53) && snake1->dir != up){snake1->dir = down;}else if (Key_state(0x41) && snake1->dir != right){snake1->dir = left;}else if (Key_state(0x44) && snake1->dir != left){snake1->dir = right;}else if (Key_state(VK_SPACE)){pause();}else if (Key_state(VK_ESCAPE)){snake1->sta = by_end;snake2->sta = by_end;}else if (Key_state(VK_F1)){if (snake1->_sleeptime >= 80){(snake1->_sleeptime) -= 20;(snake1->_weight) += 2;}setpos(70, 7);printf("奖励:%d", snake1->_weight);if (snake2->_sleeptime >= 80){(snake2->_sleeptime) -= 20;(snake2->_weight) += 2;}setpos(70, 11);printf("奖励:%d", snake2->_weight);}else if (Key_state(VK_F2)){if (snake1->_sleeptime <= 280){(snake1->_sleeptime) += 20;(snake1->_weight) -= 2;}setpos(70, 7);printf("奖励:%d", snake1->_weight);if (snake2->_sleeptime <= 280){(snake2->_sleeptime) += 20;(snake2->_weight) -= 2;}setpos(70, 11);printf("奖励:%d", snake2->_weight);}else if (Key_state(VK_UP) && snake2->dir != down){snake2->dir = up;}else if (Key_state(VK_DOWN) && snake2->dir != up){snake2->dir = down;}else if (Key_state(VK_LEFT) && snake2->dir != right){snake2->dir = left;}else if (Key_state(VK_RIGHT) && snake2->dir != left){snake2->dir = right;}snakemove(snake1,snake2);snakemove(snake2, snake1);setpos(70, 6);printf("分数:%d", snake1->_score);setpos(70, 7);printf("奖励:%d", snake1->_weight);setpos(70, 10);printf("分数:%d", snake2->_score);setpos(70, 11);printf("奖励:%d", snake2->_weight);Sleep(snake1->_sleeptime);} while (snake1->sta == ok&&snake2->sta==ok);
}
void gameend(pSnake snake1,pSnake snake2)
{/*if (snake1->sta == by_end){system("cls");setpos(48, 15);printf("游戏结束!");setpos(42, 22);system("pause");}else*/if (snake1->sta != snake2->sta){if (snake1->sta == by_body){system("cls");setpos(42, 15);printf("玩家1咬到对方蛇身了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_wall){system("cls");setpos(38, 15);printf("玩家1撞到墙了,游戏结束!");setpos(40, 22);system("pause");}/*else if (snake2->sta == by_end){system("cls");setpos(48, 15);printf("游戏结束!");setpos(42, 22);system("pause");}*/else if (snake2->sta == by_body){system("cls");setpos(34, 15);printf("玩家2咬到对方蛇身了,游戏结束!");setpos(40, 22);system("pause");}else if (snake2->sta == by_wall){system("cls");setpos(34, 15);printf("玩家2撞到墙了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_self){system("cls");setpos(34, 15);printf("玩家1咬到自己蛇身了,游戏结束!");setpos(40, 22);system("pause");}else if (snake2->sta == by_self){system("cls");setpos(34, 15);printf("玩家2咬到自己蛇身了,游戏结束!");setpos(40, 22);system("pause");}}else{if (snake1->sta == by_end){system("cls");setpos(48, 15);printf("游戏结束!");setpos(42, 22);system("pause");}else if (snake1->sta == by_body){system("cls");setpos(34, 15);printf("玩家1和玩家2都咬到对方蛇身了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_wall){system("cls");setpos(34, 15);printf("玩家1和玩家2撞到墙了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_headpush){system("cls");setpos(34, 15);printf("两位玩家相撞了,游戏结束!");setpos(40, 22);system("pause");}else if (snake1->sta == by_self){system("cls");setpos(34, 15);printf("玩家1和玩家2都咬到自己蛇身了,游戏结束!");setpos(40, 22);system("pause");}}free(snake1->_food);pSnakeNode p = snake1->_snake;pSnakeNode pre = NULL;while (p){pre = p;p = p->next;pre->next = NULL;free(pre);}free(snake1);free(snake2->_food);p = snake2->_snake;pre = NULL;while (p){pre = p;p = p->next;pre->next = NULL;free(pre);}free(snake2);
}