C语言写的一个贪吃蛇小游戏(windows系统)

正文

用C语言写的贪吃蛇小游戏,没有用到图形界面,就是在控制台上显示的简陋的小蛇蛇~

主要用到windows的API函数,小小的讲解一下:

1、system("cmd")函数传递cmd命令,可以调节控制台的字体颜色和背景颜色(当然功能远不仅如此)

2、COORD是windows.h中用表示控制台中字符坐标信息的结构体,左上角为(0,0),向右为x轴正方向,向下为y轴正方向。(注意是字符的坐标,不是指像素点坐标)

3、gotoxy函数和HideCursor函数是从网上抄的

4、关于蛇的显示问题:一开始我是每一次显示都刷新一次屏幕的,用system("cls"),很快很便捷。只不过电脑可能不这么认为。配置不好的话刷新整个控制台窗口会很卡。于是后来就改成只擦除上一次蛇出现的位置。(因为觉得我的小蛇蛇和现在花里胡哨的图形界面显得格格不入,我还专门装了个dos虚拟机,做了个dos版,具体内容放在文末

5、关于蛇的新增节位置的问题:一开始我是直接用递推公式在尾部直接新增,不一定在尾巴走过的路径上新增(如果恰好拐了个弯),虽然也可以,因为其实看不出来,但是出于强迫症最后还是新增了一个全局变量用来表示尾巴

附代码如下(dev-c++和vs均可编译通过):

/*包含文件*/
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <time.h>
#include <conio.h>

/*用链表实现蛇*/
struct snake
{
	COORD coord;          //本节位置
	struct snake* pNext;  //指向下一节
};

/*宏定义*/
#define WIDTH 40                  //窗口宽度(需为偶数)
#define HEIGHT 20                 //窗口高度 
#define LEN sizeof(struct snake)  //一节蛇的结构体长度
#define N 4                       //难度等级总个数
#define INITIAIL_LENTH 3          //初始长度 
#define LENTH_OF_WALL 4           //每面墙的长度 
#define NUMBER_OF_WALL 4         //墙面数 

/*函数声明*/
int ChooseLevel();                        //选择难度
void ChooseGameColor();                   //选择颜色 
void gotoxy(int x, int y);                //光标去到指定位置
void HideCursor(BOOL flag);               //隐藏/显示光标
void SetFood();                           //生成食物 
void PrintFood();                         //显示食物
struct snake* SnakeCreate(int lenth);     //生成蛇(可指定初始长度) 
void SnakePrint(struct snake* pHead);     //显示蛇 
void SnakeErase(struct snake* pHead);     //擦除蛇
void SnakeMove(struct snake* pHead);      //移动
void GetDirection();                      //控制转向 
int JudgeDie(struct snake* pHead);        //判断是否出界以及是否咬到自己
int JudgeGrow(struct snake* pHead);       //判断是否吃到食物
void SnakeGrow(struct snake* pHead);      //变长 
int CountScore(struct snake* pHead);      //统计得分 
void ReadArchive(int HighestScore[]);     //读取存档
void WriteArchive(int HighestScore[]);    //写入存档
void WallMaker(struct snake* pHead);      //生成墙 
void WallGetNext(int i, int j);           //随机漫步(在生成墙时用到)
void WallPrint();                         //显示墙 
void WallErase();                         //擦除墙 
int SnakeJudgeWall(struct snake* pHead);  //判断是否撞墙

/*全局变量*/
int Direction;  //蛇的方向:在每一次while循环中可能变也可能不变
COORD Food;     //食物位置:在每一次while循环中可能变也可能不变
COORD End;      //记录尾巴的位置,使新生成的一节位于原来走过的路径上

/*用二维结构体数组实现多面墙体*/
COORD wall[NUMBER_OF_WALL][LENTH_OF_WALL]; 

int main()
{
	int level, HighestScore[N],score,clock;
	struct snake* pSnakeHead;
	char repeat;
	HWND console;
	time_t t;
	system("title 贪吃蛇");
	system("mode con cols=40 lines=20");  //窗口大小,请与宏定义常量一同更改
	ChooseGameColor();
	while (1)
	{
		srand ((unsigned)time(&t)) ;
		ReadArchive(HighestScore);
		level = ChooseLevel();  //选择难度; 
		if (level < 4)
		{
			/*难度说明*/
			printf("\n  $该难度最高分为:%d\n", HighestScore[level - 1]);
			printf("\n  按任意键继续...");
			_getch();
		}
		else if(level == 4)
		{
			system("cls");
			printf("\n  #贪吃蛇·挑战模式#\n");
			printf("\n  <模式说明>\n");
			printf("\n    $该模式下\n");
			printf("\n     地图中将会出现%d组%d块连续墙体。\n",NUMBER_OF_WALL, LENTH_OF_WALL);
			printf("\n     墙体每10秒随机刷新一次位置。\n");
			printf("\n    $注意:\n\n       *墙体可能会直接出现在正前方!\n");
			printf("\n       *食物有可能被墙体挡住!\n");
			printf("\n    $该模式最高分为:%d\n", HighestScore[level - 1]);
			printf("\n  按任意键继续...");
			_getch();
		}
		system("cls");
		printf("\n  <游戏说明>\n\n    $蛇:()()()()()()[]\n\n    $初始长度:%d\n\n    $按空格可暂停\n\n  按任意键开始游戏...",INITIAIL_LENTH);
		_getch();
		system("cls");
		HideCursor(TRUE);  //隐藏光标
		pSnakeHead = SnakeCreate(INITIAIL_LENTH);  //生成蛇
		SetFood();  //设置食物
		PrintFood();  //显示食物
		SnakePrint(pSnakeHead);  //显示蛇
		if (level == 4)
		{
			WallMaker(pSnakeHead);
			WallPrint();
		}
		_getch(); 
		clock=0;
		while (1)
		{
			GetDirection();  //获取转向信息
			SnakeErase(pSnakeHead);
			SnakeMove(pSnakeHead);  //移动
			PrintFood();  //显示食物
			SnakePrint(pSnakeHead);  //显示蛇
			if (level == 4) WallPrint();
			if (JudgeDie(pSnakeHead)) break;  //判断是否Die
			if (level == 4 && SnakeJudgeWall(pSnakeHead)) break;
			if (JudgeGrow(pSnakeHead))  //判断是否吃到食物
			{
				SetFood();  //重新设置食物
				SnakeGrow(pSnakeHead);  //蛇变长
			}
			if (level < 4) Sleep(400 - 100 * level);  //利用Sleep函数控制速度 
			else if (level == 4)
			{
				Sleep(100);  //挑战模式速度
				clock++;
				if (clock == 100)
				{
					WallErase();
					WallMaker(pSnakeHead);
					WallPrint();
					clock = 0;
				}
			}
		}
		system("cls");
		score = CountScore(pSnakeHead)- INITIAIL_LENTH;
		printf("\n  <游戏结束>\n\n  $最终得分:%d\n", score);  //输出最终得分
		if (score > HighestScore[level-1])
		{
			HighestScore[level-1] = score;
			WriteArchive(HighestScore);
			printf("\n  $新纪录!\n");
		}
		printf("\n  $再来一局?输入'y'或'n':");
		HideCursor(FALSE);  //重新显示光标
		getchar();scanf("%c",&repeat); 
		if (repeat != 'y') break;
	}
	return 0;
}

int ChooseLevel()  //选择难度 
{
	int level;
	do {
		system("cls");
		printf("\n  #贪吃蛇·无尽模式#\n\n  <选择难度>\n\n    1.简单 2.一般 3.困难\n\n    4.切换至挑战模式\n\n  请选择难度:");
		scanf("%d", &level);
	} while (level < 1 || level > N);  //必须输入1~N
	return level;
}

void ChooseGameColor()
{
	int color;
	do{
		system("cls");
		system("color f0");
		printf("\n  #贪吃蛇·无尽模式#\n\n  <选择颜色>\n\n    1.黑色 2.蓝色 3.绿色 4.红色 5.紫色\n\n  请选择一个你喜欢的颜色:");
		scanf("%d",&color);
		switch(color)
		{
			case 1:break;
			case 2:system("color f9");break;
			case 3:system("color f2");break;
			case 4:system("color fc");break;
			case 5:system("color fd");break;
		}
	}while(color<1 || color>5); 
}

void gotoxy(int x, int y)  //光标去到指定位置
{
	COORD coord;
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

void HideCursor(BOOL flag) //隐藏光标 
{
	CONSOLE_CURSOR_INFO cursor;
	if (flag) cursor.bVisible = FALSE;
	else cursor.bVisible = TRUE;
	cursor.dwSize = sizeof(cursor);
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorInfo(handle, &cursor);
}

void SetFood()  //生成食物 
{
	int x;
	x = rand() % WIDTH;  //x方向上两列化为一格 
	Food.X = (x % 2 == 0) ? x : x - 1;
	Food.Y = rand() % HEIGHT;
}

void PrintFood()  //显示食物
{
	gotoxy(Food.X, Food.Y);
	printf("@@");
}

struct snake* SnakeCreate(int lenth)  //生成蛇 
{
	struct snake* pHead, * pTemp, * pNew=NULL;
	int i, x;
	pHead = (struct snake*)malloc(LEN);
	if (pHead)
	{
		x = rand() % (WIDTH - 2 * 10) + 10;  //设置与左右边框的距离,避免一出生就Die
		pHead->coord.X = (x % 2 == 0) ? x : x - 1;  //x方向上两列化为一格
		pHead->coord.Y = rand() % (HEIGHT - 2 * 5) + 5;  //设置与上下边框的距离,避免一出生就Die
		pTemp = pHead;
		Direction = rand() % 4;
		for (i = 1; i < lenth; i++)
		{
			pNew = (struct snake*)malloc(LEN);
			if (pNew)
			{
				switch (Direction)
				{
				case 0: {pNew->coord.X = (pTemp->coord.X) + 2; pNew->coord.Y = pTemp->coord.Y; }break;  //头朝左 
				case 1: {pNew->coord.X = pTemp->coord.X; pNew->coord.Y = (pTemp->coord.Y) - 1; }break;  //头朝下 
				case 2: {pNew->coord.X = (pTemp->coord.X) - 2; pNew->coord.Y = pTemp->coord.Y; }break;  //头朝右 
				case 3: {pNew->coord.X = pTemp->coord.X; pNew->coord.Y = (pTemp->coord.Y) + 1; }break;  //头朝上 
				default:printf("Direction error in SnakeCreate\n");
				}
				pTemp->pNext = pNew;
				pTemp = pNew;
			}
			else
			{
				printf("malloc error in SnakeCreate\n");
				exit(1);
			}
		}
		if(pNew) pNew->pNext = NULL;
	}
	else
	{
		printf("malloc error in SnakeCreate\n");
		free(pHead);
		exit(1);
	}
	return pHead;
}

void SnakePrint(struct snake* pHead)  //显示蛇 
{
	struct snake* p = pHead->pNext;
	gotoxy(pHead->coord.X, pHead->coord.Y);
	printf("[]");
	while (p != NULL)
	{
		gotoxy(p->coord.X, p->coord.Y);
		printf("()");
		p = p->pNext;
	}
}

void SnakeErase(struct snake* pHead)  //擦除蛇 
{
	struct snake* p = pHead;
	while(p!=NULL)
	{
		gotoxy(p->coord.X, p->coord.Y);
		printf("  ");
		p = p->pNext;
	}
}

void SnakeMove(struct snake* pHead)  //移动 
{
	int tempX_1, tempY_1, tempX_2, tempY_2;  //两对临时变量:因为需要记住前一节的位置赋给本节,同时还要保留本节位置赋给后一节
	struct snake* p;
	p = pHead;
	tempX_1 = p->coord.X;
	tempY_1 = p->coord.Y;
	switch (Direction)
	{
	case 0:p->coord.X -= 2; break;  //朝左走 
	case 1:p->coord.Y += 1; break;  //朝下走 
	case 2:p->coord.X += 2; break;  //朝右走 
	case 3:p->coord.Y -= 1; break;  //朝上走 
	default: {printf("Direction error in SnakeMove\n"); exit(1); }
	}
	p = p->pNext;
	while (p->pNext != NULL)
	{
		tempX_2 = p->coord.X;
		tempY_2 = p->coord.Y;
		p->coord.X = tempX_1;
		p->coord.Y = tempY_1;
		tempX_1 = tempX_2;
		tempY_1 = tempY_2;
		p = p->pNext;
	}
	End.X=p->coord.X;
	End.Y=p->coord.Y;
	p->coord.X = tempX_1;
	p->coord.Y = tempY_1;
}

void GetDirection()  //控制转向 
{
	char ch;
	if(_kbhit())
	{
		if ((ch=_getch())==-32) ch=_getch();
		if (ch==32) _getch();
		else if (ch==75 && Direction != 2) Direction = 0;  //向左转(增加对Direction原值的判断,防止直接掉头) 
		else if (ch==80 && Direction != 3) Direction = 1;  //向下转 
		else if (ch==77 && Direction != 0) Direction = 2;  //向右转 
		else if (ch==72 && Direction != 1) Direction = 3;  //向上转  
	}
}

int JudgeDie(struct snake* pHead)  //判断是否出界以及是否咬到自己 
{
	int flag = 0;
	struct snake* p;
	p = (pHead->pNext);
	if (pHead->coord.X<0 || pHead->coord.X>=WIDTH || pHead->coord.Y<0 || pHead->coord.Y>=HEIGHT) flag = 1;  //出界
	while (p != NULL)
	{
		if (pHead->coord.X == p->coord.X && pHead->coord.Y == p->coord.Y) flag = 1;  //咬到自己
		p = p->pNext;
	}
	return flag;
}

int JudgeGrow(struct snake* pHead)  //判断是否吃到食物 
{
	int flag = 0;
	if (pHead->coord.X == Food.X && pHead->coord.Y == Food.Y) flag = 1;
	return flag;
}

void SnakeGrow(struct snake* pHead)  //变长 
{
	struct snake* p, * pNew;
	p = pHead;
	pNew = (struct snake*)malloc(LEN);
	if (pNew)
	{
		while (p->pNext != NULL) p = p->pNext;
		pNew->coord.X =End.X; 
		pNew->coord.Y =End.Y;
		pNew->pNext = NULL;
		p->pNext = pNew;
	}
	else
	{
		printf("malloc error in SnakeMove\n");
		free(pNew);
		exit(1);
	}
}

int CountScore(struct snake* pHead)  //统计得分 
{
	struct snake* p;
	int i;
	p = pHead;
	i = 0;
	while (p != NULL)
	{
		p = p->pNext;
		i++;
	}
	return i;
}

void ReadArchive(int HighestScore[])  //读取存档
{
	int i;
	FILE* fp;
	fp = fopen("HighestScore.bin", "rb");
	if (fp==NULL)
	{
		fp = fopen("HighestScore.bin", "wb");
		for (i = 0; i < N; i++) HighestScore[i] = 0;
		fwrite(HighestScore, sizeof(int), N, fp);
		fclose(fp);
		fp=fopen("HighestScore.bin", "rb");
	}
	fread(HighestScore, sizeof(int), N, fp);
	fclose(fp);
}

void WriteArchive(int HighestScore[])  //写入存档
{
	FILE* fp;
	fp = fopen("HighestScore.bin", "wb");
	fwrite(HighestScore, sizeof(int), N, fp);
	fclose(fp);
}

void WallMaker(struct snake* pHead)
{
	int k, i, j, x, flag = 0;  //flag用于给循环提供条件
	struct snake* p = pHead;
	for (k = 0; k < NUMBER_OF_WALL; k++)
	{
		do {
			x = rand() % WIDTH;
			wall[k][0].X = (x % 2 == 0) ? x : x - 1;
			wall[k][0].Y = rand() % HEIGHT;
			flag = 0;
			while (p != NULL)  //防止与蛇重合
			{
				if (wall[k][0].X == p->coord.X && wall[k][0].Y == p->coord.Y)
				{
					flag = 1;
					p = pHead;
					break;
				}
				p = p->pNext;
			}
		} while (flag);
		i = 1;
		while (i < LENTH_OF_WALL)
		{
			WallGetNext(k, i);
			flag = 0;
			for (j = 0; j < i; j++)  //防止与已有墙体重复
			{
				if (wall[k][j].X == wall[k][i].X && wall[k][j].Y == wall[k][i].Y)
				{
					flag = 1;
					break;
				}
			}
			if (flag == 1) continue;
			if (wall[k][i].X < 0 || wall[k][i].X >= WIDTH || wall[k][i].Y < 0 || wall[k][i].Y >= HEIGHT)  //防止出界
				continue;
			while (p != NULL)  //防止与蛇重合
			{
				if (wall[k][i].X == p->coord.X && wall[k][i].Y == p->coord.Y)
				{
					flag = 1;
					p = pHead;
					break;
				}
				p = p->pNext;
			}
			if (flag == 1) continue;
			i++;
		}
	}
}

void WallGetNext(int i, int j)
{
	int dir[2] = { 1,-1 };
	switch (rand() % 2)
	{
	case 0:
	{
		wall[i][j].X = 2 * dir[rand() % 2] + wall[i][j - 1].X;
		wall[i][j].Y = wall[i][j - 1].Y;
	}break;
	case 1:
	{
		wall[i][j].X = wall[i][j - 1].X;
		wall[i][j].Y = dir[rand() % 2] + wall[i][j - 1].Y;
	}break;
	}
}

void WallPrint()
{
	int i, j;
	for (i = 0; i < NUMBER_OF_WALL; i++)
	{
		for (j = 0; j < LENTH_OF_WALL; j++)
		{
			gotoxy(wall[i][j].X, wall[i][j].Y);
			printf("[]");
		}
	}
}

void WallErase()
{
	int i, j;
	for (i = 0; i < NUMBER_OF_WALL; i++)
	{
		for (j = 0; j < LENTH_OF_WALL; j++)
		{
			gotoxy(wall[i][j].X, wall[i][j].Y);
			printf("  ");
		}
	}
}

int SnakeJudgeWall(struct snake* pHead)  //判断是否撞墙
{
	int flag = 0, i, j;
	for (i = 0; i < NUMBER_OF_WALL; i++)
		for(j=0;j<LENTH_OF_WALL;j++)
			if (pHead->coord.X == wall[i][j].X && pHead->coord.Y == wall[i][j].Y)
				flag = 1;  //撞墙
	return flag;
}

DOS版

因为觉得控制台程序与图形界面格格不入、相形见绌,就整了个DOS版。别说,玩起来还挺有感觉的。在win98上用winTC编译的。有一些要注意的点:

1、因为DOS上不能用windows.h头文件,所以坐标信息不能用COORD结构体,得自定义,或者干脆就分别用int x、int y

2、dos上没有Sleep函数,只有sleep(),是以秒为单位的,还正能传入整数。于是我自定义了一个Sleep函数。用time.h里面的CLK_TCK常量,和clock()函数。dos中CLK_TCK常量为18.2,windows里与之相对应的CLOCKS_PER_SEC值为1000。指的是一秒钟cpu时钟计数器增加多少,也就是一秒钟cpu滴答几次,大小与cpu的运算速度有关。自定义的Sleep函数代码如下(应该没错):

#include <time.h>

void Sleep(unsigned millisecond)
{
    clock_t t_0, t_1;
    t_1 = t_0 = clock();
    while(t_1 - t_0 <= millisecond * CLK_TCK / 1000)
    {
        t_1 = clock();
    }
}

3、dos上隐藏光标的函数得用另外的,如下,网上找的:

#include <dos.h>

void closecur()
{
    union REGS r;
    r.h.ah=1;
    r.h.ch=0x20;
    int86(0x10,&r,&r);
}

void opencur()
{
    union REGS r;
    r.h.ah=1;
    r.h.ch=12;
    r.h.cl=13;
    int86(0x10,&r,&r);
}

用system("cls")清屏在我win10上其实是没问题的,但是到了dos上就开始卡了。于是只好换成只擦除原来的蛇。总之就是调整了好久。

但是!但是!之前删除虚拟机的时候忘记保存里面的文件了wwwwwww[哭]。现在dos版的没有源码,只有两个exe文件了(一个是有墙的,一个是没墙的),链接如下:

链接:https://pan.baidu.com/s/1TTP8CdzWEEq7XaJ1ppMdvA 
提取码:gdsk

且玩且珍惜吧,得是纯dos系统哦。说不定哪天想不开我也许会再重新改改源代码,唉~

上一篇:C语言之链表的基本操作(含代码)


下一篇:将物联网分析从数据中心扩展到雾服务器到网络边缘