正文
用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系统哦。说不定哪天想不开我也许会再重新改改源代码,唉~