用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版。别说,玩起来还挺有感觉的。在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系统哦。说不定哪天想不开我也许会再重新改改源代码,唉~