C语言写的一个贪吃蛇小游戏(windows系统)
2021/11/28 7:13:16
本文主要是介绍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语言写的一个贪吃蛇小游戏(windows系统)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享
- 2024-11-22ansible 的archive 参数是什么意思?-icode9专业技术文章分享
- 2024-11-22ansible 中怎么只用archive 排除某个目录?-icode9专业技术文章分享
- 2024-11-22exclude_path参数是什么作用?-icode9专业技术文章分享
- 2024-11-22微信开放平台第三方平台什么时候调用数据预拉取和数据周期性更新接口?-icode9专业技术文章分享
- 2024-11-22uniapp 实现聊天消息会话的列表功能怎么实现?-icode9专业技术文章分享
- 2024-11-22在Mac系统上将图片中的文字提取出来有哪些方法?-icode9专业技术文章分享
- 2024-11-22excel 表格中怎么固定一行显示不滚动?-icode9专业技术文章分享
- 2024-11-22怎么将 -rwxr-xr-x 修改为 drwxr-xr-x?-icode9专业技术文章分享
- 2024-11-22在Excel中怎么将小数向上取整到最接近的整数?-icode9专业技术文章分享