|
马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 sswy 于 2018-8-2 03:14 编辑
- //头文件需要用的函数
- #include <stdio.h>//标准输入输出的库函数
- #include <time.h>//要用随机数所以用time函数
- #include <Windows.h>//需要,控制DOS界面,要在DOS界面中设置文字颜色,获取坐标位置
- #include <stdlib.h>//宏定义和通用工具函数
- #include <conio.h>//接收键盘输入输出
- //宏定义用来定义上下左右方向,把方向定义成整型的数字,方便在后面代码中进行逻辑运算
- #define U 1
- #define D 2
- #define L 3
- #define R 4
- //全局变量
- typedef struct Snake //蛇身的一个节点
- {
- int x; //用来存放节点X坐标
- int y; //用来存放节点Y坐标
- struct Snake *next;//蛇身的下一个节点
- }SNAKE;
- int score = 0, add = 10;//总得分,每次吃食物得分
- int HighScore = 0;//游戏最高分
- int sleeptime = 200;//sleeptime每次运行的时间间隔
- int status; //方向
- SNAKE *head, *food;//蛇头指针和食物指针
- SNAKE *q;//便利蛇身
- int endgamestatus = 0;//表示游戏状态结束的情况,因为有3种情况,咬到自己,撞到墙,主动退出
- HANDLE hOut;//为了使用windows函数,建立句柄,它本身是void *
- /*************函数声明**************/
- void gotoxy(int x, int y);//光标位置函数
- int color(int c); //设置文字颜色
- void printsnake(); //开始界面蛇绘制
- void welcometogame(); //开始界面
- void creatMap(); //绘制游戏地图
- void scoreandtips(); //游戏界面右侧的得分和小提示
- void initsnake(); //初始化蛇身,绘制蛇身
- void createfood(); //创建并随机出现食物
- int biteself(); //判断是否咬到了自己
- void cantcrosswall(); //设置蛇撞墙情况
- void speedup(); //加速
- void speeddown(); //减速
- void snakemove(); //控制蛇前进方向
- void keyboardControl(); //控制键盘按键
- void lostdraw(); //游戏结束界面
- void endgame(); //游戏结束
- void choose(); //游戏失败后的选择
- void File_out(); //存储最高分进文件
- void File_in(); //在文件种读取最高分
- void explation(); //游戏说明
- //设置颜色函数
- int color(int c)
- {
- SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);//设置颜色
- return 0;
- }
- //控制台移动函数
- void gotoxy(int x, int y)
- {
- COORD c;//通过修改pos.X和pos.Y的值就可以实现光标的位置控制。
- c.X = x;
- c.Y = y;
- SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), c);//控制光标位置
- }
- //在界面中绘制蛇
- void printsnake()
- {
- //利用建立好的移动函数和颜色函数绘制图形
- color(6);//用颜色函数给字体上色
- gotoxy(35, 1);//移动光标到合适位置,打印出图案
- printf("/^\\/^\");//注意哦C语言中要答应出\需要多加一个\
- gotoxy(34, 2);//移动光标到第二行,打印出图案
- printf("|_| o|");
- gotoxy(33, 2);
- color(2);
- printf("_");
- gotoxy(25, 3);
- color(12);
- printf("\\/"); //蛇信
- gotoxy(31, 3);
- color(2);
- printf("/");
- gotoxy(37, 3);
- color(6);
- printf(" \\_/"); //蛇眼睛
- gotoxy(41, 3);
- color(10);
- printf(" \");
- gotoxy(26, 4);
- color(12);
- printf("\\____"); //舌头
- gotoxy(32, 4);
- printf("_________/");
- gotoxy(31, 4);
- color(2);
- printf("|");
- gotoxy(43, 4);
- color(10);
- printf("\");
- gotoxy(32, 5);
- color(2);
- printf("\\_______"); //蛇嘴
- gotoxy(44, 5);
- color(10);
- printf("\");
- gotoxy(39, 6);
- printf("| | \"); //下面都是画蛇身
- gotoxy(38, 7);
- printf("/ / \");
- gotoxy(37, 8);
- printf("/ / \\ \");
- gotoxy(35, 9);
- printf("/ / \\ \");
- gotoxy(34, 10);
- printf("/ / \\ \");
- gotoxy(33, 11);
- printf("/ / _----_ \\ \");
- gotoxy(32, 12);
- printf("/ / _-~ ~-_ | |");
- gotoxy(31, 13);
- printf("( ( _-~ _--_ ~-_ _/ |");
- gotoxy(32, 14);
- printf("\\ ~-____-~ _-~ ~-_ ~-_-~ /");
- gotoxy(33, 15);
- printf("~-_ _-~ ~-_ _-~");
- gotoxy(35, 16);
- printf("~--______-~ ~-___-~");
- }
- //欢迎界面
- void welcometogame()
- {
- int n;
- int i, j = 1;
- color(11);//设置"贪吃蛇大作战"字体颜色
- //gotoxy(43, 18);//调整光标位置
- //printf("贪 吃 蛇 大 作 战");//打印
- color(14);//设置边框颜色
- for (i = 20; i <= 26; i++)//移动纵横坐标光标,打印边框
- {
- for (j = 27; j < 74; j++)
- {
- gotoxy(j, i);//移动光标 x不停移动j就是x
- if (i == 20 || i == 26)//横向定位打印范围 打印横边框
- {
- printf("-");
- }
- else if (j == 27 || j == 73)//横向定位2条边的位置才打印 打印竖边框
- {
- printf("|");
- }
- }
- }
- //打印选择文字
- color(12);
- gotoxy(35, 22);
- printf("1.开始游戏");
- gotoxy(55, 22);
- printf("2.游戏说明");
- gotoxy(35, 24);
- printf("3.退出游戏");
- //打印下方小文字并根据输入进入函数
- color(3);
- gotoxy(29, 27);
- printf("请选择[1 2 3]:[ ]\b\b");// \b就是光标退格
- color(14);//输入选择的文字
- scanf("%d", &n);//输入选择
- switch (n)
- {
- case 1:
- system("cls");//发出DOS命令,清屏
- creatMap();
- initsnake();
- createfood();
- break;
- case 2:
- break;
- case 3: exit(0);
- break;
- default:
- printf("乱输入我就退出\n");
- break;
- }
- }
- //创建地图
- void creatMap()
- {
- int i, j;
- //打印上下边框(外围)
- for (i = 0; i < 58; i += 2)//因为小方括占用2个字符,所以是i+=2
- {
- color(5);//边框颜色
- gotoxy(i, 0);//上坐标
- printf("●");
- gotoxy(i, 26);//下坐标
- printf("●");
- }
- //打印左右竖边框(外围)
- for (i = 1; i < 26; i++)
- {
- gotoxy(0, i);//打印左边竖边框
- printf("●");
- gotoxy(56, i);//打印右边竖边框
- printf("●");
- }
- //打印棋盘内部
- for (i = 2; i < 56; i += 2)
- {
- for (j = 1; j < 26; j++)
- {
- gotoxy(i, j);
- color(3);
- printf("●\n\n");
- }
- }
- }
- //创建地图右侧信息
- void scoreandtips()//此函数应该在键盘控制的函数中调用,因为每次按键,如果蛇吃到食物,得分都会变化
- {
- //File_out();//读取游戏最高分函数
- //打印最高记录和当前得分信息
- color(11);
- gotoxy(64, 4);
- printf("最高记录:%d", HighScore);
- color(11);
- gotoxy(64, 8);
- printf("得分:%d", score);
- //打印小提示
- color(13);//设置颜色
- gotoxy(73, 11);//移动光标
- printf("小提示");//打印
- color(6);//设置颜色
- gotoxy(60, 13);//移动光标
- printf("------------------------------------");//打印
- gotoxy(60, 25);//移动光标
- printf("------------------------------------");//打印
- color(3);
- gotoxy(64, 14);
- printf("每个食物得分:%d分", add);
- gotoxy(64, 16);
- printf("不能撞墙,不能咬到自己");
- gotoxy(64, 18);
- printf("用↑ ↓ ← →分别控制蛇的移动");
- gotoxy(64, 20);
- printf("F1 为加速,F2 为减速");
- gotoxy(64, 22);
- printf("SPACE:暂停游戏");
- gotoxy(64, 24);
- printf("ESC :退出游戏");
- }
- //读取文本中最高分
- void File_in()
- {
- FILE *fp;
- fp = fopen("save.txt", "a+");//a+以附加的方式,打开文件,文件不存在创建文件
- fscanf(fp, "%d", &HighScore);//把读取文件中数据给全局变量
- fclose(fp);
- }
- //绘制蛇身,其实就是定位蛇的初始位置,调整光标位置,打印结构体
- void initsnake()
- {
- SNAKE *tail;//蛇身
- int i;
- tail = (SNAKE *)malloc(sizeof(SNAKE));//创建蛇尾
- tail->x = 24;//蛇尾坐标定义
- tail->y = 5;
- tail->next = NULL;
- for (i = 1; i <= 4; i++)//分配蛇的光标位置
- {
- head = (SNAKE *)malloc(sizeof(SNAKE));//创建蛇头
- head->next = tail;//指向蛇身
- head->x = 24 + 2 * i;//2代表一个格子
- head->y = 5;//纵向坐标y,y不动和尾巴在同一排
- tail = head;//类似头插法
- }
-
- //打印蛇身
- while (tail != NULL)//因为tail = head,所以tail在for结束后是头指针
- {
- gotoxy(tail->x, tail->y);//根据每一个结构体坐标调整光标位置,并打印出来
- color(14);
- printf("★");
- tail = tail->next;
- }
- }
- //创建并随机出现食物
- void createfood()
- {
- SNAKE *food_1=NULL;//创建食物指针
- srand((unsigned)time(NULL));//随机种子
- food_1 = (SNAKE*)malloc(sizeof(SNAKE));//给食物内存
- food_1->next = NULL;
- while ((food_1->x % 2) != 0)//限制食物只会出现在棋盘中的坐标,准备把坐标给到移动光标,绘制食物 %2这里余2是要求食物在棋盘格中间
- {
- food_1->x = rand() % 25 + 2;//棋盘格子宽度是52 让食物出现在X轴最小限制2-52之间 2因为是2个字符为一个格子
- }
- food_1->y = 24 + 1;//棋盘格子长度是24 让食物出现在Y轴最小限制1-24之间 1因为是纵向1个字符为1列
- q = head;//取得蛇头头指针
-
- //int num = 20;//递归返回的时候可以记录进入递归之前的值,但是如果是指针,无法记录之前的
- char ch = 'n';
- while ( q != NULL)//蛇任何一个部分都不能和食物重叠
- {
- //num -= 1;
- if (q->x == food_1->x && q->y == food_1->y)//如果随机出现的食物坐标和蛇初始化坐标重叠,删除这个食物内容,重新创建
- {
- free(food_1);//释放当前食物内容
- /*
- food_1 = (SNAKE*)malloc(sizeof(SNAKE));
- while ((food_1->x % 2) != 0)
- {
- food_1->x = rand() % 52 + 2;
- food_1->y = 24 + 1;
- q = head;
- }
- */
- ch = 'y';
- createfood();//递归重新创建食物坐标,但是这里注意递归进去后,循环q到NULL运行完毕,函数返回来位置在if(ch=='y'),q坐标已经被移动到NULL了,指针无法记录之前的
-
- }
- if (ch == 'y')
- {
- food_1 = food;
- return;
- }
- q = q->next;//蛇身上任何坐标不能和食物重合
- if (q == NULL)
- {
- break;
- }
- }
- gotoxy(food_1->x, food_1->y);//把随机好的食物坐标给到移动光标里,并绘制出来
- free(food);
- food = food_1;//把食物指针给出去全局指针
-
- color(12);//食物颜色
- printf("●");//绘制食物
- }
- //判断蛇咬到自己
- int biteself()
- {
- SNAKE *self;
- q = head;
- self = head;
- while (q ->next!= NULL)//任何一个点碰到身体算失败
- {
- q = q->next;
- if (self->x == q->x && self->y == q->y)
- {
- return 1;//返回1表示蛇咬到了自己
- }
- }
- return 0;
- }
- //判断蛇是否撞墙
- void cantcrosswall()
- {
- if (head->x == 0 || head->x == 56 || head->y == 0 || head->y == 26)
- {
- endgamestatus = 1;//撞墙情况的失败界面,给全局
- printf("撞墙失败");
- exit(1);
- return;
- }
- }
- //蛇加速前进
- void speedup()
- {
- if (sleeptime >= 50)//限制50的刷新率,
- {
- sleeptime = sleeptime - 10; //时间间隔 - 10 刷新更快了
- add = add + 2;//同时每吃一个食物的得分+2,可叠加再吃到14再吃到16 add全局基础得分
- }
- }
- //蛇减速减速
- void speeddown()
- {
- if (sleeptime < 350)//限制时间间隔小于350不能太慢了哈
- {
- sleeptime = sleeptime + 30;//加大刷新间隔,速度就慢了
- add = add - 2;//减速了游戏就很容易,所以降低基础分
- if (sleeptime == 350)//当游戏最慢的时候,基础分为1
- {
- add = 1;
- }
- }
- }
- //不按键时蛇前进,蛇的动力都在这里,这个程序就类似,1帧一个执行,1帧一个执行,可以这样想象,用程序执行间隔Sleep控制蛇的运行速度
- void snakemove()
- {
- //这个动力其实就是把新的结构体节点,根据当前蛇头位置移动一个位置,然后不断捕捉键盘方向,来循环移动
- SNAKE *nexthead;//表示蛇头下一个位置,新的头指针,代表新的位置,类似移动了一步
- cantcrosswall();//判断蛇是否撞墙
- nexthead = (SNAKE*)malloc(sizeof(SNAKE));//为下一步开辟一个空间
-
- if (status == U)//向上移动一个格子,其实就是根据头指针位置放一个新结构体,再删除最后的尾巴
- {
- //不断向上前进
- nexthead->x = head->x; //新节点保持当前横向坐标不变 ,因为我们要上移一格,所以直接改Y就好了
- nexthead->y = head->y - 1; //新节点,上移动一个光标位置,因为y是间隔1个字符,所以-1
- nexthead->next = head; //链接老蛇头,并把当前地址设成最新蛇头
- head = nexthead;
- q = head;//给一个全局傀儡方便控制
- if (nexthead->x == food->x && nexthead->y == food->y)
- {
-
- while (q != NULL)//当上一移动位置和食物位置重叠,就把当前位置画出来,并不删除尾巴上的
- {
- gotoxy(q->x, q->y);
- color(14);
- printf("★");
- q = q->next;
- }
- score = score + add;//食物吃到后加10分
- speedup();//加速 速度初始200 并且提高下次吃到食物的基础分数
- createfood();//重新生成食物
- }
- else//如果没有吃到食物就把痕迹删除
- {
-
- while (q->next->next != NULL)//当上一移动位置和食物位置没有重叠,就打印当前蛇,但是要把最后一个痕迹删除
- {
- gotoxy(q->x, q->y);
- color(14);
- printf("★");
- q = q->next;
- }
- gotoxy(q->next->x, q->next->y); //清除蛇尾巴,因为移动了一个位置,就要把后面的去除并还原棋盘格
- color(3);
- printf("■");
- free(q->next); //释放尾巴结构体
- q->next = NULL; //当前结构体,指向空
- }
- }
- if (status == D)//向上移动一个格子,其实就是根据头指针位置放一个新结构体,再删除最后的尾巴
- {
- //不断向上前进
- nexthead->x = head->x; //新节点保持当前横向坐标不变 ,因为我们要上移一格,所以直接改Y就好了
- nexthead->y = head->y+1; //新节点,上移动一个光标位置,因为y是间隔1个字符,所以-1
- nexthead->next = head; //链接老蛇头,并把当前地址设成最新蛇头
- head = nexthead;
- q = head;//给一个全局傀儡方便控制
- if (nexthead->x == food->x && nexthead->y == food->y)
- {
-
- while (q != NULL)//当上一移动位置和食物位置重叠,就把当前位置画出来,并不删除尾巴上的
- {
- gotoxy(q->x, q->y);
- color(14);
- printf("★");
- q = q->next;
- }
- score = score + add;//食物吃到后加10分
- speedup();//加速 速度初始200 并且提高下次吃到食物的基础分数
- createfood();//重新生成食物
- }
- else//如果没有吃到食物就把痕迹删除
- {
-
- while (q->next->next != NULL)//当上一移动位置和食物位置没有重叠,就打印当前蛇,但是要把最后一个痕迹删除
- {
- gotoxy(q->x, q->y);
- color(14);
- printf("★");
- q = q->next;
- }
- gotoxy(q->next->x, q->next->y); //清除蛇尾巴,因为移动了一个位置,就要把后面的去除并还原棋盘格
- color(3);
- printf("■");
- free(q->next); //释放尾巴结构体
- q->next = NULL; //当前结构体,指向空
- }
- }
- if (status == L)//向上移动一个格子,其实就是根据头指针位置放一个新结构体,再删除最后的尾巴
- {
- //不断向上前进
- nexthead->x = head->x-2; //新节点保持当前横向坐标不变 ,因为我们要上移一格,所以直接改Y就好了
- nexthead->y = head->y; //新节点,上移动一个光标位置,因为y是间隔1个字符,所以-1
- nexthead->next = head; //链接老蛇头,并把当前地址设成最新蛇头
- head = nexthead;
- q = head;//给一个全局傀儡方便控制
- if (nexthead->x == food->x && nexthead->y == food->y)
- {
-
- while (q != NULL)//当上一移动位置和食物位置重叠,就把当前位置画出来,并不删除尾巴上的
- {
- gotoxy(q->x, q->y);
- color(14);
- printf("★");
- q = q->next;
- }
- score = score + add;//食物吃到后加10分
- speedup();//加速 速度初始200 并且提高下次吃到食物的基础分数
- createfood();//重新生成食物
- }
- else//如果没有吃到食物就把痕迹删除
- {
-
- while (q->next->next != NULL)//当上一移动位置和食物位置没有重叠,就打印当前蛇,但是要把最后一个痕迹删除
- {
- gotoxy(q->x, q->y);
- color(14);
- printf("★");
- q = q->next;
- }
- gotoxy(q->next->x, q->next->y); //清除蛇尾巴,因为移动了一个位置,就要把后面的去除并还原棋盘格
- color(3);
- printf("■");
- free(q->next); //释放尾巴结构体
- q->next = NULL; //当前结构体,指向空
- }
- }
- if (status == R)//向上移动一个格子,其实就是根据头指针位置放一个新结构体,再删除最后的尾巴
- {
- //不断向上前进
- nexthead->x = head->x + 2; //新节点保持当前横向坐标不变 ,因为我们要上移一格,所以直接改Y就好了
- nexthead->y = head->y; //新节点,上移动一个光标位置,因为y是间隔1个字符,所以-1
- nexthead->next = head; //链接老蛇头,并把当前地址设成最新蛇头
- head = nexthead;
- q = head;//给一个全局傀儡方便控制
- if (nexthead->x == food->x && nexthead->y == food->y)
- {
-
- while (q != NULL)//当上一移动位置和食物位置重叠,就把当前位置画出来,并不删除尾巴上的
- {
- gotoxy(q->x, q->y);
- color(14);
- printf("★");
- q = q->next;
- }
- score = score + add;//食物吃到后加10分
- speedup();//加速 速度初始200 并且提高下次吃到食物的基础分数
- createfood();//重新生成食物
- }
- else//如果没有吃到食物就把痕迹删除
- {
-
- while (q->next->next != NULL)//当上一移动位置和食物位置没有重叠,就打印当前蛇,但是要把最后一个痕迹删除
- {
- gotoxy(q->x, q->y);
- color(14);
- printf("★");
- q = q->next;
- }
- gotoxy(q->next->x, q->next->y); //清除蛇尾巴,因为移动了一个位置,就要把后面的去除并还原棋盘格
- color(3);
- printf("■");
- free(q->next); //释放尾巴结构体
- q->next = NULL; //当前结构体,指向空
- }
- }
- if (biteself() == 1)//判断是否吃到自己 为什么先判断撞墙呢,因为要判断当前蛇头位置是否和最开始的边沿重合
- //而吃到自己是要在自己移动后判断是否蛇头和身体重合
- {
- endgamestatus = 2;
- }
- }
- //控制键盘按键
- void keyboardControl()
- {
- status = R;//设置初始移动方向为右
- while (1)//可以一直按键所以设置循环
- {
- scoreandtips();//游戏右侧得分小提示
- if (GetAsyncKeyState(VK_UP) && status != D)//运行方向不是向下,就可以记录上
- {
- status = U;
- }
- else if (GetAsyncKeyState(VK_DOWN) && status != U)
- {
- status = D;
- }
- else if (GetAsyncKeyState(VK_LEFT) && status != R)
- {
- status = L;
- }
- else if (GetAsyncKeyState(VK_RIGHT) && status != L)
- {
- status = R;
- }
- if (GetAsyncKeyState(VK_SPACE))//如果按下空格键,暂停游戏300毫秒
- {
- Sleep(300);
- if (GetAsyncKeyState(VK_SPACE))
- {
- break;
- }
- }
- else if (GetAsyncKeyState(VK_ESCAPE))//如果按了ESC键就退出游戏
- {
- endgamestatus = 3;
- break;
- }
- else if (GetAsyncKeyState(VK_F1))//如果按了F1键就加速
- {
- speedup();
- }
- else if (GetAsyncKeyState(VK_F2))//如果按了F1键就加速
- {
- speeddown();
- }
- Sleep(sleeptime);//控制刷新率
- snakemove();
- }
- }
- int main(void)//每一个函数解决一个单一问题,进行组合,在以后编程中也要这样
- {
- system("mode con cols=100 lines=30");//让C语言发一个DOS命令,设置窗口宽度高度
- printsnake();//输出绘制图案
- welcometogame();//输出边框和文
- //File_in();//把文件中最高分读取进来
-
- keyboardControl();
- return 0;
- }
复制代码
代码有点长,但是只需要看 <创建并随机出现食物函数> 那里就好了。
问题是这样的:在游戏初始化的时候,随机出现食物,代码如下,并且食物如果随机出现的坐标和蛇身坐标重合,就重新定位食物坐标。
实现方式我用了递归和常规2种方式,常规的没有问题,但是递归的在游戏运行的时候就会卡死,关键是我找了很久很久问题所在都觉得是正确的,但是程序就是要卡死。希望老师们能看看这个隐藏问题,当研究研究玩耍,但是请b21老师绕道,哈哈哈
while ( q != NULL)//蛇任何一个部分都不能和食物重叠
{
//num -= 1;
if (q->x == food_1->x && q->y == food_1->y)//如果随机出现的食物坐标和蛇初始化坐标重叠,删除这个食物内容,重新创建
{
free(food_1);//释放当前食物内容
/*
food_1 = (SNAKE*)malloc(sizeof(SNAKE));
while ((food_1->x % 2) != 0)
{
food_1->x = rand() % 52 + 2;
food_1->y = 24 + 1;
q = head;
}
*/
ch = 'y';
createfood();//递归重新创建食物坐标,但是这里注意递归进去后,循环q到NULL运行完毕,函数返回来位置在if(ch=='y'),q坐标已经被移动到NULL了,指针无法记录之前的
}
if (ch == 'y')
{
food_1 = food;
return;
}
q = q->next;//蛇身上任何坐标不能和食物重合
if (q == NULL)
{
break;
}
}
y坐标一直是24 + 1
一直是25
x坐标虽然是随机的,但是很难超过20
如果蛇占用了这些位置,那就一直递归
|
|