此片文章涉及到到控制台设置的相关操作,虚拟键码,宽字符输出等,有些地方大家可能会看不懂,可以阅读以下文章来进一步了解:
控制台程序设置-CSDN博客
效果展示:
QQ2024428-181932
源码已放在文章结尾
目录
一.功能实现
二.头文件的声明
三.游戏开始 -- GameStart
1.窗口设置
2.光标设置
2.1光标的隐藏
2.2光标坐标设置--SetPos
3.欢迎界面打印--WelcomeToGame
4.地图绘制--CreateMap
5.蛇身初始化--InitSnake
6.食物坐标设定--CreateFood
四.游戏运行-- GameRun
1.打印玩法说明
2.打印当前得分和食物分数
3.获取按键状态--Key
4.蛇的移动-Snakemove
4.1.计算新节点的坐标
4.2.是否撞墙--KillByWall
4.3.是否撞到自身--KillByself
5.循环进行
五.游戏结束 -- GameEnd
1.打印游戏结束的原因
2.释放空间
六.游戏的优化
七.优化后源码
首先我们需要对默认窗口做一下更改,如下:
一.功能实现
- 地图绘制
- 蛇撞墙死亡
- 蛇撞自身死亡
- 用↓ → ← ↑来操作蛇的方向
- 蛇吃食物加分并记录总得分
- 蛇的加减功能
- 蛇的速度越快每个食物分数越高
- 蛇每吃一个食物身体加长
- 空格键暂停游戏,再次点击开始游戏
二.头文件的声明
我们把该项目分为三部分,分别是头文件Snake.h,源文件Snake.c,源文件test.c
以下是头文件上的声明:
#pragma
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<locale.h>
#include<windows.h>
#include<stdbool.h>
#include<time.h>
#define pos_x 24//蛇的初始横坐标
#define pos_y 5 //蛇的初始纵坐标
#define WALL L'□' //墙的形状
#define BODY L'●' //蛇身的形状
#define FOOD L'★'//食物的形状
#define Key(x) (GetAsyncKeyState(x)&1)//判断按键是否被按过
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 Snake//蛇的维护
{
pSnakeNode _pSnake;//蛇的坐标
pSnakeNode _pFood;//食物的坐标
enum DIRECTION _dir;//蛇的移动方向
enum GAME_STATUS _status;//蛇的状态
int _food_weight;//一个食物的分数
int _score;//总分数
int _sleep_time;//蛇的速度
}Snake,*pSnake;
三.游戏开始 -- GameStart
在做准备工作中需要在主函数中进行本地化,以方便后面输出宽字符。如下:
setlocale(LC_ALL, "");
1.窗口设置
在游戏开始之前我们需要对运行窗口进行设定,首先我们对它的标题进行设置,如下:
system("title 贪吃蛇");
其次是窗口大小的设定,比如设置一个100行,30列的窗口,如下:
system("mode con cols=100 lines=30");
2.光标设置
2.1光标的隐藏
为了方便玩家的体验,我们把光标隐藏,也就是把它的透明度该为fales,如下:
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取柄
CONSOLE_CURSOR_INFO Info;//申请CONSOLE_CURSOR_INFO类型的变量,用于储存坐标信息
GetConsoleCursorInfo(houtput, &Info);//获取坐标信息
Info.bVisible = false;//改变坐标透明度
SetConsoleCursorInfo(houtput, &Info);//设置坐标
2.2光标坐标设置--SetPos
在做打印时候需要将光标移动到合适的坐标位置,而这个操作到后面是会频繁操作的,所以我们把它封装成一个函数,如下:
void SetPos(int x, int y)//设置光标的坐标
{
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//GetStdHandle获取柄,HANDLE接收柄
COORD pos = { x,y };//坐标
SetConsoleCursorPosition(houtput, pos);
}
3.欢迎界面打印--WelcomeToGame
欢迎界面我们可以玩家打印一些欢迎界面和玩法说明,在此之前我们需要把光标调到适应的位置,使其把信息打印到中间位置效果更佳。如下:
上图的窗口右下角的 请按任意键继续...是代码system("pause"); 带来的效果,它作用是暂停程序的运行,按任意键可以使程序继续运行。
4.地图绘制--CreateMap
在地图创建中我们是用宽字符 '□'去构造的,而宽字符占两个字节。蛇身我们用宽字符'●'去构造,而后面的设计中是要判断蛇是否撞墙的,所以在打印 '□' 和 '●' 之前光标都要移动到横坐标为偶数的坐标位置,或都要移动到横坐标为奇数的坐标位置,这样可以保证不会出现蛇头的一半在墙里面,一半在墙外面的情况,如这样:
其次要保证食物的横坐标奇偶性和蛇横坐标保持一致,也是为了避免这种情况的发生。(这里我们选择让蛇,墙体,食物的横坐标都为偶数)
那么接下来打印地图就比较简单了,如下:
void CreateMap()//地图绘制
{
for (int i = 0; i <= 56; i += 2)
wprintf(L"%lc", WALL);
SetPos(0, 26);
for (int i = 0; i <= 56; i+=2)
wprintf(L"%lc", WALL);
for (int i = 1; i <= 26; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
for (int i = 1; i < 26; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
效果如下:(当然此图并不是该代码的输出结果)
5.蛇身初始化--InitSnake
蛇在初始化状态,我们可以用5个节点来给蛇初始化并打印,当然还需要初始化蛇的移动方向,蛇的状态,食物的分数,总分数,速度(休眠时间)。
void InitSnake(pSnake ps)//蛇身的初始化
{
ps->_pSnake = NULL;//重点!!
pSnakeNode pnew = NULL;
for (int i = 0; i < 5; i++)
{
pnew = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(pnew);
pnew->x = pos_x + i * 2;
pnew->y = pos_y;
pnew->next = NULL;
if ((ps->_pSnake) == NULL)
{
ps->_pSnake = pnew;
}
else
{
pnew->next = ps->_pSnake;
ps->_pSnake = pnew;
}
}
pnew = ps->_pSnake;
while (pnew)
{
SetPos(pnew->x, pnew->y);
wprintf(L"%lc", BODY);
pnew = pnew->next;
}
ps->_status = OK;
ps->_food_weight = 10;
ps->_score = 0;
ps->_sleep_time = 200;
ps->_dir = RIGHT;
}
6.食物坐标设定--CreateFood
食物的坐标是随机的,需要一个随机数生成函数,虽然说是随机但也有前提条件:
1.食物不能在墙上和墙以外
2.食物的横坐标必须是偶数(地图绘制那里讲过原因,这里不在赘述)
3.食物不能在蛇的身上
反过来说如果产生的随机数是这两个条件中的任意一个都不能要需要重新生成。这里用 go to语句更为方便。
代码实现:
void CreateFood(pSnake ps)
{
int x, y;
pSnakeNode cur = NULL;
again:
cur = ps->_pSnake;
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;
}
cur = cur->next;
}
pSnakeNode Food = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(Food);
Food->x = x;
Food->y = y;
Food->next = NULL;
ps->_pFood = Food;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
这里已把srand((unsigned int)time(NULL));放在了主函数main中
四.游戏运行-- GameRun
1.打印玩法说明
在游戏运行过程中我们可以把玩法说明打印到右侧空白的地方,注意打印前需要调整光标的坐标位置,包括后面涉及到的所有打印。
2.打印当前总得分和食物分数
在打印食物分数和总分数过程中要注意这两项是跟着蛇的运动而改变的,需要和蛇的移动一起放在一个循环中。
3.获取按键状态--Key
我们通过获取键盘按键状态,来相应的改变蛇的运动方向和速度
要注意几个点:
1.蛇不能向反方向走,比如蛇的方向原本是向右的但玩家按取一个 ← 按键应该示为无效。
2.玩家按空格键后如何让游戏暂停是个问题,我们可以用一个死循环的睡眠来实现,并且如果再次按空格键退出循环。其次如果需要把 "游戏已暂停,点击空格键继续" 打印到屏幕上的话,当游戏再次开始后此字符串的位置应用空格字符来填充,否则就会一直停留在屏幕上。
3.蛇的速度不能无限的小或无限的大要不然玩家无法玩,应该设置一个上下限。
代码如下:
if (Key(VK_UP) && ps->_dir != DOWN)
ps->_dir = UP;
else if (Key(VK_DOWN) && ps->_dir != UP)
ps->_dir = DOWN;
else if (Key(VK_LEFT) && ps->_dir != RIGHT)
ps->_dir = LEFT;
else if (Key(VK_RIGHT) && ps->_dir != LEFT)
ps->_dir = RIGHT;
if (Key(VK_SPACE))
{
while (1)
{
SetPos(29,13);
printf("游戏已暂停,点击空格键继续");
if (Key(VK_SPACE))
break;
Sleep(300);
}
SetPos(29, 13);
int x = 26;
while (x--)
printf(" ");
}
if (Key(VK_ESCAPE))
ps->_status = END_NORMAL;
if (Key(VK_SHIFT))
{
if (ps->_sleep_time >= 80)
{
ps->_sleep_time -=30 ;
ps->_food_weight += 2;
}
}
if (Key(VK_CONTROL))
{
if (ps->_sleep_time <= 320)
{
ps->_sleep_time += 30;
ps->_food_weight -= 2;
}
}
4.蛇的移动-Snakemove
在写此函数的时候我们先想一个问题如何实现动态呢?有人可能会想每输出一次做一次清屏处理。
这确实是行得通的。但在此我们用另一种方法,当下一个节点无食物的时候,将下一个节点头插到原头节点上并删除尾节点然后打印出整个蛇,并且将原来的尾用空格覆盖,否则它会一直停留在屏幕上。当下一个节点有食物的时候,将下一个节点头插到原头节点上但尾节点不用删不能用空格覆盖,并重新构造新的食物。效果如下:
当下一个节点无食物的时候:
当下一个节点有食物的时候:
所以在打印之前我们需要判断下一个节点是否为食物,然后以不同的方式打印。
4.1.计算新节点的坐标
在蛇的移动过程中我们需要不断的插入头节点并删除尾节点,插入什么样的头节点是根据从玩家获取到的按键状态来决定的,比如一个正在向右移动的蛇,玩家按了 ↑ 按键,那么我们创造的下一个节点内储存的横坐标就应是原头节点的横坐标,纵坐标应是原头节点纵坐标减1。以此类推。
代码如下:
void SnakeMove(pSnake ps)
{
pSnakeNode pnew = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(pnew);
switch (ps->_dir)
{
case UP:
pnew->y = ps->_pSnake->y - 1;
pnew->x = ps->_pSnake->x;
break;
case DOWN:
pnew->y = ps->_pSnake->y + 1;
pnew->x = ps->_pSnake->x;
break;
case LEFT:
pnew->x = ps->_pSnake->x - 2;
pnew->y = ps->_pSnake->y;
break;
case RIGHT:
pnew->x = ps->_pSnake->x + 2;
pnew->y = ps->_pSnake->y;
break;
}
pnew->next = ps->_pSnake;
ps->_pSnake = pnew;
if ((pnew->x == ps->_pFood->x )&& (pnew->y == ps->_pFood->y))//是食物
{
int i = 112;
while (pnew)
{
SetPos(pnew->x, pnew->y);
color(i++);
wprintf(L"%lc", BODY);
pnew = pnew->next;
if (i == 126)
i = 112;
if (i == 119||i==112)
i++;
}
color(112);
ps->_score += ps->_food_weight;
free(ps->_pFood);
printf("\a");
CreateFood(ps);
}
else//不是食物
{
while (pnew->next->next)
{
SetPos(pnew->x, pnew->y);
color(120);
wprintf(L"%lc", BODY);
pnew = pnew->next;
}
color(112);
SetPos(pnew->next->x, pnew->next->y);
printf(" ");
free(pnew->next);
pnew->next = NULL;
}
}
4.2.是否撞墙--KillByWall
在判断是否撞到墙,我们只需要判断头节点坐标是否和墙体坐标相同,如果相同就把蛇的状态对应改变就可以。代码如下:
void KillByWall(pSnake ps)//判断是否撞墙
{
if ((ps->_pSnake->y == 0) || (ps->_pSnake->y == 26)
|| (ps->_pSnake->x == 0) || (ps->_pSnake->x == 56))
ps->_status = KILL_BY_WALL;
}
4.3.是否撞到自身--KillByself
判断是否撞到自身只需要判断头节点的坐标是否和蛇身体坐标相同,如果相同的话同样是把蛇的状态对应改变。代码如下:
void KillByself(pSnake ps)//判断是否撞到自己
{
pSnakeNode pr = ps->_pSnake;
pSnakeNode pu = ps->_pSnake->next;
while (pu)
{
if (pu->x == pr->x && pu->y == pr->y)
{
ps->_status = KILL_BY_SELF;
return;
}
pu = pu->next;
}
}
5.循环进行
以上的操作只是完成了蛇的一次移动,我们需要一个do while语句使它循环起来,不过为了控制蛇的速度在此之前我们需要加上一个Sleep睡眠,如下:
Sleep(ps->_sleep_time);
然后就是在考虑一下循环条件,即
ps->_status == OK
如下:
do
{
......
Sleep(ps->_sleep_time);
} while (ps->_status == OK);
五.游戏结束 -- GameEnd
1.打印游戏结束的原因
这个操作也是很简单我们只需要检查一下蛇的状态然后用 if 语句进行条件输出就可以。
到这里我们可能就会发现设置蛇的状态的必要性,和用枚举类型来做该设置的优势,其次上面的循环也用到了蛇的状态。
2.释放空间
注意我们在写程序的过程中使用的蛇身和食物都是用malloc动态申请的空间,最好呢是我们手动把它们释放掉。
void GameEnd(pSnake ps)//游戏结束善后处理
{
SetPos(29, 13);
if (ps->_status == KILL_BY_SELF)
{
color(116);
printf("你撞到了自己,游戏结束");
}
if (ps->_status == KILL_BY_WALL)
{
color(116);
printf("你撞到了墙,游戏结束");
}
free(ps->_pFood);
while (ps->_pSnake)
{
pSnakeNode cur = ps->_pSnake->next;
free(ps->_pSnake);
ps->_pSnake = cur;
}
}
六.游戏的优化
当我们学会了以上这些操作以后,我们就可以根据自己的需要来提升游戏的效果。
例如:
1.给蛇,食物,墙体设置颜色。
2.把蛇的身体设置成不同的形状。
2.添加双人模式,添加食物个数。
... ...
这里我来提一下双人模式的设计,比如我们想要实现的功能是:
- 玩家1控制的蛇撞到玩家2控制蛇的身体,那么玩家1就死亡。
- 玩家撞到自己的身体并不会死亡。
- 任意玩家按空格键使该游戏暂停。
- 食物的数量为10个。
- 两玩家可以分别控制自己的蛇的速度,让博弈更激烈。
第1,2点呢比较简单,第3点的实现我们可以把食物做成10个节点的链表,当一个食物被吃掉的时候就更新该食物对应的节点所储存的坐标并再次打印出食物。
而然难点是在于如何让蛇的速度不能互相干扰,为了解决这个问题我们可以用一个双线程来同时处理两个玩家控制的蛇相当于把它们分别放在不同时空分别运行,这样它们的时间就可以互不干扰,但它们又共用一些资源比如SetPos函数,也就是它们同时控制光标的位置,这就会导致光标的位置随着时间随意乱窜打印的并不是我们想要的结果,这样我们考虑用一个时间锁来处理这个问题。
七.优化后源码
Snake.h
#pragma
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<locale.h>
#include<windows.h>
#include<stdbool.h>
#include<time.h>
//蛇的初始坐标
#define pos_x 24
#define pos_y 5
//单人模式 地图坐标
#define X 58
#define Y 26
//双人模式 地图坐标
#define X2 98
#define Y2 38
#define WALL L'□'//地图边界
#define BODY L'●'//蛇身
#define FOOD L'★'//食物
#define Key(x) (GetAsyncKeyState(x)&1)//键盘敲击信息读取
CRITICAL_SECTION cs;
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 Snake
{
pSnakeNode _pSnake;//蛇的坐标
pSnakeNode _pFood;//食物的坐标
enum DIRECTION _dir;//蛇的移动方向
enum GAME_STATUS _status;//蛇的状态
int _food_weight;//一个食物的分数
int _score;//分数
int _sleep_time;//蛇的速度
}Snake, * pSnake;
typedef struct LSnake
{
pSnake p1;
pSnake p2;
int x;
}LSnake,*pLSnake;
int menu();//菜单打印
void color(int x);//颜色设置
void SetPos(int x, int y);//光标位置设定
int GameStart(pSnake ps1,pSnake ps2); //游戏开始
int WelcomeToGame();//欢迎界面
void PrintHelpInfo1();//单人模式 玩法说明
void PrintHelpInfo2();//双人模式 玩法说明
void CreateMap1();//单人模式 地图绘制
void CreateMap2();//双人模式 地图绘制
void InitSnake1(pSnake ps);//单人模式 蛇身的初始化
void InitSnake2(pSnake ps);//双人模式 蛇身的初始化
void CreateFood1(pSnake ps);//单人模式 食物坐标的设定
void CreateFood2(pSnake ps1, pSnake ps2);//双人模式 食物坐标的设定
void PrintFood(pSnakeNode hfood);//打印食物
DWORD WINAPI th1(LPVOID ps);//玩家1的线程
DWORD WINAPI th2(LPVOID ps);//玩家2的线程
void GameRun1(pSnake ps);//单人模式 游戏运行主逻辑
void GameRun2(pSnake ps1, pSnake ps2);//双人模式 游戏运行主逻辑
void KillByWallp(pSnake ps1);
void KillByWall(pSnake ps);//判断是否撞墙
void KillByself(pSnake ps);//判断是否撞到自己
void KillByselfp(pSnakeNode pnew, pSnake ps);//是否撞到对方玩家身体
pSnakeNode OpFood(pSnakeNode ps, pSnakeNode hfood);//判断是否吃到食物
void SnakeMove1(pSnake ps);//单人模式 蛇的移动
void SnakeMove2(pSnake ps1, pSnake ps2);//双人模式 蛇的移动
void GameEnd(pSnake ps);//单人模式 游戏结束善后
void GameEndp(pSnake ps1, pSnake ps2);//双人模式 游戏结束善后
Snake .c
#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"
#define Key(x) (GetAsyncKeyState(x)&1)
void color(int x)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);
}
void SetPos(int x, int y)//设置光标的坐标
{
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//GetStdHandle获取柄,HANDLE接收柄
COORD pos = { x,y };//坐标
SetConsoleCursorPosition(houtput, pos);
}
int menu()
{
SetPos(36, 10);
printf("******** "); color(121); printf("1.单人模式 "); color(112); printf("*********");
SetPos(36, 11);
printf("******** "); color(124); printf("2.双人博弈 "); color(112); printf("*********");
SetPos(38, 12);
printf("请选择>>");
char c, k;
int x = 0,i=0;
while (scanf("%c%c", &c, &k),c != '1' && c!= '2')
{
SetPos(36, 13+i++);
color(116);
printf("输入错误,请输入1或2");
color(112);
}
x = c - '0';
return x;
}
int WelcomeToGame()//欢迎界面打印
{
SetPos(40, 10);
printf("欢迎来到贪吃蛇游戏!");
SetPos(80, 28);
system("pause");
int x = menu();
system("cls");
if (x == 1)
{
SetPos(5, 10);
printf("请用键盘按键↑← → ↓来控制蛇的移动方向,空格键暂停,Ese键退出游戏,");
printf("蛇吃到食物会增加长度和分数,蛇撞到自身或墙壁游戏失败,");
printf("Tab键加速,Q键减速");
}
else
{
SetPos(5, 10);
printf("玩家一:请用键盘按键↑← → ↓来控制蓝蛇的移动方向,Alt右键加速, Ctrl右键减速");
SetPos(5, 12);
printf("玩家二:请用键盘按键 W A S D 来控制绿蛇的移动方向,Alt左键加速,C键减速");
SetPos(16, 14);
printf("空格键暂停游戏");
}
SetPos(80, 28);
system("pause");
system("cls");
return x;
}
void CreateMap1()//地图绘制
{
for (int i = 0; i < X; i += 2)
{
wprintf(L"%lc", WALL);
}
SetPos(0, Y);
for (int i = 0; i < X; i += 2)
{
wprintf(L"%lc", WALL);
}
for (int i = 1; i <= Y; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
for (int i = 1; i < Y; i++)
{
SetPos(X-2, i);
wprintf(L"%lc", WALL);
}
}
void CreateMap2()//地图绘制
{
for (int i = 0; i < X2; i += 2)
{
wprintf(L"%lc", WALL);
}
SetPos(0, Y2);
for (int i = 0; i < X2; i += 2)
{
wprintf(L"%lc", WALL);
}
for (int i = 1; i <= Y2; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
for (int i = 1; i < Y2; i++)
{
SetPos(X2-2, i);
wprintf(L"%lc", WALL);
}
}
void InitSnake1(pSnake ps)//玩家一蛇身的初始化
{
ps->_pSnake = NULL;//重点!!
pSnakeNode pnew = NULL;
for (int i = 0; i < 5; i++)
{
pnew = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(pnew);
pnew->x = pos_x + i * 2;
pnew->y = pos_y;
pnew->next = NULL;
if ((ps->_pSnake) == NULL)
{
ps->_pSnake = pnew;
}
else
{
pnew->next = ps->_pSnake;
ps->_pSnake = pnew;
}
}
pnew = ps->_pSnake;
while (pnew)
{
SetPos(pnew->x, pnew->y);
wprintf(L"%lc", BODY);
pnew = pnew->next;
}
ps->_status = OK;
ps->_food_weight = 10;
ps->_score = 0;
ps->_sleep_time = 200;
ps->_dir = RIGHT;
}
void InitSnake2(pSnake ps)//蛇身的初始化
{
ps->_pSnake = NULL;//重点!!
pSnakeNode pnew = NULL;
for (int i = 0; i < 5; i++)
{
pnew = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(pnew);
pnew->x = 24 + i * 2;
pnew->y = 10;
pnew->next = NULL;
if ((ps->_pSnake) == NULL)
{
ps->_pSnake = pnew;
}
else
{
pnew->next = ps->_pSnake;
ps->_pSnake = pnew;
}
}
pnew = ps->_pSnake;
while (pnew)
{
SetPos(pnew->x, pnew->y);
wprintf(L"%lc", BODY);
pnew = pnew->next;
}
ps->_status = OK;
ps->_food_weight = 10;
ps->_score = 0;
ps->_sleep_time = 200;
ps->_dir = RIGHT;
}
void PrintFood(pSnakeNode hfood)//打印食物
{
pSnakeNode ps = hfood;
int i = 112;
while (ps)
{
color(i++);
SetPos(ps->x, ps->y);
wprintf(L"%lc", FOOD);
ps = ps->next;
if(i==119)
i++;
}
color(112);
}
void CreateFood2(pSnake ps1,pSnake ps2)
{
int x=0, y=0,n=10;
pSnakeNode cur1 = NULL,cur2=NULL,Food1=NULL,hFood1=NULL;
pSnakeNode Food2 = NULL, hFood2 = NULL;
pSnakeNode hfood = NULL;
while (n--)
{
again:
cur1 = ps1->_pSnake;
cur2 = ps2->_pSnake;
do
{
x = rand() % (X2 - 4) + 2;
y = rand() % (Y2 - 1) + 1;
} while (x % 2 != 0);
while (cur1)
{
if (x == cur1->x && y == cur1->y)
{
goto again;
}
cur1 = cur1->next;
}
while (cur2)
{
if (x == cur2->x && y == cur2->y)
{
goto again;
}
cur2 = cur2->next;
}
while (hfood)
{
if (x == hfood->x && y == hfood->y)
{
goto again;
}
hfood = hfood->next;
}
pSnakeNode pnew = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(pnew);
pnew->x = x;
pnew->y = y;
pnew->next = NULL;
if (!hFood1)
{
Food1 = pnew;
hFood1 = Food1;
hfood = Food1;
}
else
{
Food1->next = pnew;
Food1 = Food1->next;
}
}
ps1->_pFood = hFood1;
ps2->_pFood = hFood1;
PrintFood(hFood1);
}
void CreateFood1(pSnake ps)
{
int x, y;
pSnakeNode cur = NULL;
again:
cur = ps->_pSnake;
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
while (cur)
{
if (x == cur->x && y == cur->y)
{
goto again;
}
cur = cur->next;
}
pSnakeNode Food = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(Food);
Food->x = x;
Food->y = y;
Food->next = NULL;
ps->_pFood = Food;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
int GameStart(pSnake ps1, pSnake ps2)//开始游戏
{
system("mode con cols=100 lines=35 ");
system("title 贪吃蛇");
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO Info;
GetConsoleCursorInfo(houtput, &Info);
Info.bVisible = false;
SetConsoleCursorInfo(houtput, &Info);
int x=WelcomeToGame();//欢迎界面
if (x == 2)
{
system("mode con cols=150 lines=42 ");
}
color(116);
if (x == 1)
CreateMap1();//地图绘制
else
CreateMap2();
color(112);
if (x == 1)
{
InitSnake1(ps1);//蛇身的初始化
CreateFood1(ps1);
}
else
{
InitSnake1(ps1);
InitSnake2(ps2);
CreateFood2(ps1,ps2);//食物坐标的设定
}
return x;
}
void PrintHelpInfo1()//打印玩法
{
SetPos(60, 15);
printf("请用键盘按键↑← → ↓来控制蛇的移动方向");
SetPos(60, 16);
printf("蛇吃到食物会增加长度和分数");
SetPos(60, 17);
printf("蛇撞到自身或墙壁游戏失败");
SetPos(60, 18);
printf("Tab键加速,Q键减速");
SetPos(60, 19);
printf("空格键表示暂停,Ese键退出游戏");
SetPos(60, 1);
}
void PrintHelpInfo2()//打印玩法
{
SetPos(X2+6, 15);
printf("玩家1请用键盘按键↑← → ↓来控制蛇的移动方向");
SetPos(X2 + 6, 16);
printf("玩家2请用键盘按键 W A S D 来控制蛇的移动方向");
SetPos(X2 + 6, 18);
printf("蛇吃到食物会增加长度和分数");
SetPos(X2 + 6, 19);
printf("蛇撞到自己或另一玩家不会死亡,蛇撞墙壁游戏失败");
SetPos(X2 + 6, 20);
printf("Shift加速,Ctrl减速");
SetPos(X2 + 6, 21);
printf("空格键表示暂停,Ese键退出游戏");
SetPos(X2 + 6, 1);
}
void SnakeMove1(pSnake ps)单人模式
{
pSnakeNode pnew = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(pnew);
switch (ps->_dir)
{
case UP:
pnew->y = ps->_pSnake->y - 1;
pnew->x = ps->_pSnake->x;
break;
case DOWN:
pnew->y = ps->_pSnake->y + 1;
pnew->x = ps->_pSnake->x;
break;
case LEFT:
pnew->x = ps->_pSnake->x - 2;
pnew->y = ps->_pSnake->y;
break;
case RIGHT:
pnew->x = ps->_pSnake->x + 2;
pnew->y = ps->_pSnake->y;
break;
}
pnew->next = ps->_pSnake;
ps->_pSnake = pnew;
if ((pnew->x == ps->_pFood->x) && (pnew->y == ps->_pFood->y))
{
while (pnew)
{
SetPos(pnew->x, pnew->y);
color(116);
wprintf(L"%lc", BODY);
pnew = pnew->next;
}
color(112);
ps->_score += ps->_food_weight;
free(ps->_pFood);
printf("\a");
CreateFood1(ps);
}
else
{
while (pnew->next->next)
{
SetPos(pnew->x, pnew->y);
color(120);
wprintf(L"%lc", BODY);
pnew = pnew->next;
}
color(112);
SetPos(pnew->next->x, pnew->next->y);
printf(" ");
free(pnew->next);
pnew->next = NULL;
}
Sleep(ps->_sleep_time);
}
void ChangeFood(pSnake ps1,pSnake ps2,pSnakeNode ps)//改变食物位置
{
pSnakeNode cur1 = NULL, cur2 = NULL,hfood=ps2->_pFood;
int x = 0, y = 0;
Again:
cur1 = ps1->_pSnake;
cur2 = ps2->_pSnake;
do
{
x = rand() % (X2 - 4) + 2;
y = rand() % (Y2 - 1) + 1;
} while (x % 2 != 0);
while (cur1)
{
if (x == cur1->x && y == cur1->y)
{
goto Again;
}
cur1 = cur1->next;
}
while (cur2)
{
if (x == cur2->x && y == cur2->y)
{
goto Again;
}
cur2 = cur2->next;
}
while (hfood)
{
if (x == hfood->x && y == hfood->y)
{
goto Again;
}
hfood = hfood->next;
}
ps->x = x, ps->y = y;
}
pSnakeNode OpFood(pSnakeNode ps, pSnakeNode hfood)
{
pSnakeNode hf = hfood;
while (hf)
{
if (ps->x == hf->x && ps->y == hf->y)
return hf;
hf = hf->next;
}
return NULL;
}
//玩家1///
DWORD WINAPI th1(LPVOID ps)
{
pLSnake psk = (pLSnake)ps;
pSnake pw1 = NULL, pw2 = NULL;
pw1 = psk->p1;
pw2 = psk->p2;
int fup = 0;
do
{
EnterCriticalSection(&cs);
color(112);
SetPos(X2 + 6, 8);
printf("玩家1 绿蛇");
LeaveCriticalSection(&cs);
EnterCriticalSection(&cs);
SetPos(X2 + 6, 9);
if (fup == 1)
{
printf("食物分数:"); color(116); printf("%2d", pw1->_food_weight); color(112);
}
else if (fup == -1)
{
printf("食物分数:"); color(114); printf("%2d", pw1->_food_weight); color(112);
}
else
{
printf("食物分数:%2d", pw1->_food_weight);
}
LeaveCriticalSection(&cs);
EnterCriticalSection(&cs);
color(112);
SetPos(X2 + 6, 10);
printf("总得分:%d", pw1->_score);
LeaveCriticalSection(&cs);
if (Key(VK_UP) && pw1->_dir != DOWN)
pw1->_dir = UP;
else if (Key(VK_DOWN) && pw1->_dir != UP)
pw1->_dir = DOWN;
else if (Key(VK_LEFT) && pw1->_dir != RIGHT)
pw1->_dir = LEFT;
else if (Key(VK_RIGHT) && pw1->_dir != LEFT)
pw1->_dir = RIGHT;
if (Key(VK_SPACE))
{
EnterCriticalSection(&cs);
SetPos(29, 13);
printf("游戏已暂停,点击空格键继续");
while (1)
{
Sleep(200);
if (Key(VK_SPACE))
break;
}
int x = 26;
SetPos(29, 13);
while (x--)
printf(" ");
LeaveCriticalSection(&cs);
}
if (Key(0XA5))
{
if (pw1->_sleep_time >= 80)
{
pw1->_sleep_time -= 30;
pw1->_food_weight += 2;
fup = 1;
}
}
if (Key(0XA3))
{
if (pw1->_sleep_time <= 320)
{
pw1->_sleep_time += 30;
pw1->_food_weight -= 2;
fup = -1;
}
}
///
pSnakeNode pnew = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(pnew);
switch (pw1->_dir)
{
case UP:
pnew->y = pw1->_pSnake->y - 1;
pnew->x = pw1->_pSnake->x;
break;
case DOWN:
pnew->y = pw1->_pSnake->y + 1;
pnew->x = pw1->_pSnake->x;
break;
case LEFT:
pnew->x = pw1->_pSnake->x - 2;
pnew->y = pw1->_pSnake->y;
break;
case RIGHT:
pnew->x = pw1->_pSnake->x + 2;
pnew->y = pw1->_pSnake->y;
break;
}
pnew->next = pw1->_pSnake;
pw1->_pSnake = pnew;
KillByselfp(pnew, pw2);
KillByWallp(pw1);
pSnakeNode k = OpFood(pnew, pw1->_pFood);
int x1 = X2, y1 = Y2;
if (k)
{
x1 = pnew->x + 2;
y1 = pnew->y + 2;
EnterCriticalSection(&cs);
SetPos(x1, y1);
color(116);
printf("+%d", pw1->_food_weight);
LeaveCriticalSection(&cs);
int i = 112;
while (pnew)
{
EnterCriticalSection(&cs);
SetPos(pnew->x, pnew->y);
wprintf(L"%lc", BODY);
LeaveCriticalSection(&cs);
color(i++);
pnew = pnew->next;
if (i == 126)
i = 112;
if (i == 119 || i == 112)
i++;
}
color(112);
printf("\a");
ChangeFood(pw1, pw2, k);
PrintFood(pw1->_pFood);
pw1->_score += pw1->_food_weight;
}
else
{
while (pnew->next->next)
{
EnterCriticalSection(&cs);
SetPos(pnew->x, pnew->y);
color(114);
wprintf(L"%lc", BODY);
LeaveCriticalSection(&cs);
pnew = pnew->next;
}
color(112);
EnterCriticalSection(&cs);
SetPos(pnew->next->x, pnew->next->y);
printf(" ");
LeaveCriticalSection(&cs);
free(pnew->next);
pnew->next = NULL;
}
Sleep(pw1->_sleep_time);
EnterCriticalSection(&cs);
SetPos(x1, y1);
printf(" ");
LeaveCriticalSection(&cs);
} while (pw2->_status == OK && pw1->_status == OK);
return 0;
}
//玩家2///
DWORD WINAPI th2(LPVOID ps)
{
pLSnake psk = (pLSnake)ps;
pSnake pw1 = NULL, pw2 = NULL;
pw1 = psk->p1;
pw2 = psk->p2;
int fp = 0;
do {
EnterCriticalSection(&cs);
color(112);
SetPos(X2 + 6, 12);
printf("玩家2 蓝蛇");
LeaveCriticalSection(&cs);
EnterCriticalSection(&cs);
SetPos(X2 + 6, 13);
if (fp == 1)
{
printf("食物分数:"); color(116); printf("%2d", pw2->_food_weight); color(112);
}
else if (fp == -1)
{
printf("食物分数:"); color(114); printf("%2d", pw2->_food_weight); color(112);
}
else
{
printf("食物分数:%2d", pw2->_food_weight);
}
LeaveCriticalSection(&cs);
EnterCriticalSection(&cs);
color(112);
SetPos(X2 + 6, 14);
printf("总得分:%2d", pw2->_score);
LeaveCriticalSection(&cs);
if (Key(0x57) && pw2->_dir != DOWN)
pw2->_dir = UP;
else if (Key(0x53) && pw2->_dir != UP)
pw2->_dir = DOWN;
else if (Key(0x41) && pw2->_dir != RIGHT)
pw2->_dir = LEFT;
else if (Key(0x44) && pw2->_dir != LEFT)
pw2->_dir = RIGHT;
if (Key(0xA4))
{
if (pw2->_sleep_time >= 80)
{
pw2->_sleep_time -= 30;
pw2->_food_weight += 2;
fp = 1;
}
}
if (Key(0x43))
{
if (pw2->_sleep_time <= 320)
{
pw2->_sleep_time += 30;
pw2->_food_weight -= 2;
fp = -1;
}
}
pSnakeNode pnew = (pSnakeNode)malloc(sizeof(SnakeNode));
assert(pnew);
switch (pw2->_dir)
{
case UP:
pnew->y = pw2->_pSnake->y - 1;
pnew->x = pw2->_pSnake->x;
break;
case DOWN:
pnew->y = pw2->_pSnake->y + 1;
pnew->x = pw2->_pSnake->x;
break;
case LEFT:
pnew->x = pw2->_pSnake->x - 2;
pnew->y = pw2->_pSnake->y;
break;
case RIGHT:
pnew->x = pw2->_pSnake->x + 2;
pnew->y = pw2->_pSnake->y;
break;
}
pnew->next = pw2->_pSnake;
pw2->_pSnake = pnew;
KillByselfp(pnew, pw1);
KillByWallp(pw2);
pSnakeNode k = OpFood(pnew, pw2->_pFood);
int x2 = X2, y2 = Y2;
if (k)
{
x2 = pnew->x + 2;
y2 = pnew->y + 2;
EnterCriticalSection(&cs);
SetPos(x2, y2);
color(116);
printf("+%d", pw2->_food_weight);
LeaveCriticalSection(&cs);
int i = 112;
while (pnew)
{
EnterCriticalSection(&cs);
SetPos(pnew->x, pnew->y);
color(i++);
wprintf(L"%lc", BODY);
LeaveCriticalSection(&cs);
pnew = pnew->next;
if (i == 126)
i = 112;
if (i == 119 || i == 112)
i++;
}
color(112);
printf("\a");
ChangeFood(pw1, pw2, k);
PrintFood(pw2->_pFood);
pw2->_score += pw2->_food_weight;
}
else
{
while (pnew->next->next)
{
EnterCriticalSection(&cs);
SetPos(pnew->x, pnew->y);
color(121);
wprintf(L"%lc", BODY);
LeaveCriticalSection(&cs);
pnew = pnew->next;
}
EnterCriticalSection(&cs);
SetPos(pnew->next->x, pnew->next->y);
printf(" ");
LeaveCriticalSection(&cs);
free(pnew->next);
pnew->next = NULL;
}
Sleep(pw2->_sleep_time);
EnterCriticalSection(&cs);
SetPos(x2, y2);
printf(" ");
LeaveCriticalSection(&cs);
} while (pw2->_status == OK&&pw1->_status==OK);
return 0; // 可返回任意值
}
void KillByWall(pSnake ps)//判断是否撞墙
{
if ((ps->_pSnake->y == 0) || (ps->_pSnake->y == Y)
|| (ps->_pSnake->x == 0) || (ps->_pSnake->x == X-2))
ps->_status = KILL_BY_WALL;
}
void KillByWallp(pSnake ps)//判断是否撞墙
{
if ((ps->_pSnake->y == 0) || (ps->_pSnake->y == Y2)
|| (ps->_pSnake->x == 0) || (ps->_pSnake->x == X2 - 2))
ps->_status = KILL_BY_WALL;
}
void KillByself(pSnake ps)//判断是否撞到自己
{
pSnakeNode pr = ps->_pSnake;
pSnakeNode pu = ps->_pSnake->next;
while (pu)
{
if (pu->x == pr->x && pu->y == pr->y)
{
ps->_status = KILL_BY_SELF;
return;
}
pu = pu->next;
}
}
void KillByselfp(pSnakeNode pnew, pSnake ps)//判断是否撞到对方
{
pSnakeNode pu = ps->_pSnake->next;
while (pu)
{
if (pnew->x == pu->x && pnew->y == pu->y)
{
ps->_status = KILL_BY_SELF;
return;
}
pu = pu->next;
}
}
void GameRun1(pSnake ps)//游戏运行
{
PrintHelpInfo1();
int fuk = 0;
do
{
SetPos(64, 12);
printf("总得分:%d", ps->_score);
SetPos(64, 13);
if (fuk == 1)
{
printf("食物分数:"); color(116); printf("%2d", ps->_food_weight); color(112);
}
else if (fuk == -1)
{
printf("食物分数:"); color(114); printf("%2d", ps->_food_weight); color(112);
}
else
{
printf("食物分数:%2d", ps->_food_weight);
}
if (Key(VK_UP) && ps->_dir != DOWN)
ps->_dir = UP;
else if (Key(VK_DOWN) && ps->_dir != UP)
ps->_dir = DOWN;
else if (Key(VK_LEFT) && ps->_dir != RIGHT)
ps->_dir = LEFT;
else if (Key(VK_RIGHT) && ps->_dir != LEFT)
ps->_dir = RIGHT;
if (Key(VK_SPACE))
{
while (1)
{
SetPos(29, 13);
printf("游戏已暂停,点击空格键继续");
if (Key(VK_SPACE))
break;
Sleep(300);
}
SetPos(29, 13);
int x = 26;
while (x--)
printf(" ");
}
if (Key(VK_ESCAPE))
ps->_status = END_NORMAL;
if (Key(0x09))
{
if (ps->_sleep_time >= 80)//if (ps->_sleep_time >= 30)
{
ps->_sleep_time -= 30;
ps->_food_weight += 2;
fuk = 1;
}
}
if (Key(0x51))
{
if (ps->_sleep_time <= 320)
{
ps->_sleep_time += 30;
ps->_food_weight -= 2;
fuk = -1;
}
}
SnakeMove1(ps);
KillByself(ps);
KillByWall(ps);
} while (ps->_status == OK);
}
void GameRun2(pSnake pu1, pSnake pu2)
{
LSnake kp;
pLSnake psk = &kp;
psk->p1 = pu1;
psk->p2 = pu2;
HANDLE thp1 = NULL, thp2 = NULL;
// 初始化临界区
InitializeCriticalSection(&cs);
// 创建线程
thp1 = CreateThread(NULL, 0, th1, (LPVOID)psk, 0, NULL);
thp2 = CreateThread(NULL, 0, th2, (LPVOID)psk, 0, NULL);
assert(thp1);
assert(thp2);
// 等待线程结束
WaitForSingleObject(thp1, INFINITE);
WaitForSingleObject(thp2, INFINITE);
// 销毁临界区
DeleteCriticalSection(&cs);
// 关闭线程句柄
CloseHandle(thp1);
CloseHandle(thp2);
}
void GameEnd(pSnake ps)//游戏结束善后处理
{
SetPos(29, 13);
if (ps->_status == KILL_BY_SELF)
{
color(116);
printf("你撞到了自己,游戏结束");
}
if (ps->_status == KILL_BY_WALL)
{
color(116);
printf("你撞到了墙,游戏结束");
}
free(ps->_pFood);
while (ps->_pSnake)
{
pSnakeNode cur = ps->_pSnake->next;
free(ps->_pSnake);
ps->_pSnake = cur;
}
}
void GameEndp(pSnake ps1, pSnake ps2)
{
SetPos(29, 13);
if (ps1->_status == KILL_BY_WALL)
{
color(116);
printf("玩家1撞到了墙,玩家2获胜");
}
while (ps1->_pSnake)
{
pSnakeNode cur = ps1->_pSnake->next;
free(ps1->_pSnake);
ps1->_pSnake = cur;
}
SetPos(29, 14);
if (ps2->_status == KILL_BY_WALL)
{
color(116);
printf("玩家2撞到了墙,玩家1获胜");
}
SetPos(29, 15);
if (ps2->_status == KILL_BY_SELF)
{
color(116);
printf("玩家2死亡,玩家1获胜");
}
SetPos(29, 15);
if (ps1->_status == KILL_BY_SELF)
{
color(116);
printf("玩家1死亡,玩家2获胜");
}
free(ps2->_pFood);
while (ps2->_pSnake)
{
pSnakeNode cur = ps2->_pSnake->next;
free(ps2->_pSnake);
ps2->_pSnake = cur;
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Snake.h"
void test()
{
Snake ps1,ps2;
ps1._pFood = NULL, ps2._pFood = NULL;
ps1._pSnake = NULL, ps2._pSnake = NULL;
setlocale(LC_ALL, "");
srand((unsigned int)time(NULL));
int x = 0;
do
{
x = GameStart(&ps1,&ps2);
if (x == 1)
{
GameRun1(&ps1);
GameEnd(&ps1);
}
else
{
GameRun2(&ps1, &ps2);
GameEndp(&ps1,&ps2);
}
SetPos(27, 16);
printf("M键重新开始游戏,Esc键退出");
color(112);
while (1)
{
if (Key(VK_ESCAPE))
return;
if (Key(0x4D))
break;
Sleep(23);
}
} while (1);
if (x == 1)
SetPos(0, Y + 1);
else
SetPos(0, Y2 + 1);
}
int main()
{
test();
color(127);
return 0;
}