Ciallo!这里是一位即将步入大学的小菜鸡一枚,三个月的假期加上疫情原因延迟开学的我完全受不了了啦,所以不如坐下来好好学习一下大学要学的c语言。(我的专业是物联网啊淦,听说物联网学的东西又多又杂,现在的我很方张 ~ o(* ̄▽ ̄*)o)
经过几天的努力,终于把c语言的一些基础概念搞清楚了(超级摸的好不好),此时我想起我之前用python写的一个输入字符串-用电脑读出来的一段代码(下一次发?那个贼简单),欸,突然有个编写贪吃蛇的代码想法,不过我决定先用自己的思路来构思一下。
先放一张效果图,最后做出来的效果挺不错,还是保留了一些原本的特性。
目标拆解
贪吃蛇咋做呢,就用命令提示符窗口来做吧。第一步先实现游戏的地图,第二步实现贪吃蛇的位置、长度和移动,第三步实现贪吃蛇的穿墙或撞墙,第四步实现食物的位置,,第五步实现游戏的胜利和失败。啊,感觉好复杂。
啊,这位靓仔,实现自己想法那种事情啦,很coooooool的啦,慢慢看下去了啦。(我好机车哦)
第一步
先构思一下贪吃蛇的图形。欸,直接用printf打印出来就不用啥外部插件实现图形化了啊,可以用ascii码来表示蛇和食物,空格来表示格子。那么怎么做呢,那就用一个长度为100的数组,然后打印数组每个数且每10格换个行,酱紫10*10的格子就做粗来啦。
声明一个长度为100的数组,然后给每个元素赋值为32(ascii码32为空格),输出时用%c将数组的元素转换为ascii对应的字符。
int board[100];
memset(board,32,sizeof(board));
for(int i=0;i<100;i++){ //输出完整网格
if(i%10==0){
printf("\n%c ",board[i]);
}
else{
printf("%c ",board[i]);
}
}
这里用余数==0来表示第10格,那第0格咋办,用 "%c\n" 输出就会变成第一行只有一个,那就用“”“\n%c” 直接在第一个或十一个元素前换行,相当于在本该第二行的第一个前换行。
但在这里又遇到了一个问题,网格是做出来了,那怎么刷新呢,我苦思冥想,终于,经过百度我想起来了命令提示符cls这个命令 ,这个命令可以清除命令提示符内的所有内容。不断的打印和清楚,就像放动画一样。
system()函数可以输入cmd命令。
#include<windows.h>
system("cls");
(可以欸,这样甚至可以实现动画欸,超酷的好不好)
第二步
1、既然用数组来存储了地图,那就在数组里改变元素的值(打印根据数组元素的值打印空格或者字符),这样就和地图一起打印出来了。
2、那么最难的一步来了,怎么实现贪吃蛇的长度呢,想了半天想到了一个笨方法(自己想的当然没有网上的各种高级方法精妙),那就把蛇身的位置都用另一个数组记录下来,(因为我地图使用的是一维数组,所以蛇身的每一个位置其实就是一个数字,把这个数字存储到另一个数组就ok啦)
//这是数组每一个元素赋值为其下标时打印的结果
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99
比如说,蛇身数组存储了[45,46,47,0,0,....],蛇的长度变量 length 为2 (代码中简写为l,其实可以看成增加的长度),那么蛇头的位置就是47,那么只要在输出地图的时候将45,46,47三个位置的元素的值改变,那么输出地图的时候,这三个位置就是蛇的位置了。
int body[100]={0}; //声明蛇身数组
int l =0 ; //这里为了方便对应数组,就将蛇的长度声明为0,数组第一个元素下标为0
for(int i=l;i>=0;i--){ //将蛇身的位置对应到棋盘上
board[body[i]]=79; //从下标为l的数组开始,body[i]是蛇头的位置
} //board[body[i]]=79就是将地图上蛇的位置的ascii码变为79,就是0
3、 那么蛇移动的时候怎么办呢。
a. 首先先获取移动的方向,通过一个if判断键盘按下,否则代码会一直停在这等你按下一个键。
然后获取键盘值,用switch对应键盘值给移动方向变量move赋值。如果输入方向和移动的方向相反则不改变方向。这里可以在代码上方加一句Sleep(500),让代码等待500毫秒,但等待的时候输入的字符仍会被下方代码检测到,这样做出来的贪吃蛇操纵更加流畅。
int move; //移动的方向
if(_kbhit()){ //检测键盘按下
Fx=_getch(); //获取键盘值
switch (Fx){
case 119 : if(move==2){move=2;}else{move=8;};break; //根据返回值给move赋值,并且不能反方向
case 115 : if(move==8){move=8;}else{move=2;};break;
case 97 : if(move==6){move=6;}else{move=4;};break;
case 100 : if(move==4){move=4;}else{move=6;};break;
default :break;
}
}
b.然后是蛇头的移动了。 我们把原本是一维的数组通过打印换行的方式实现了二维的表现,那么只要在一维的数据上改变就能实现对二维表现的更改。形象的来说,如果蛇头向上移动了,蛇头的位置就-10,在图上的表现就是蛇头位置55变成了45,向上移动了一格。那么根据刚刚的移动方向变量move移动蛇头的位置place。
switch (move)
{
case 8:place -=10;break;
case 2:place +=10;break;
case 4:place -=1 ;break;
case 6:place +=1 ;break;
default:
break;
}
c.蛇头移动了,那么蛇身怎么办呢。那就每移动一次,蛇身数组中的元素赋值为后一个元素的值。还是[45,46,47,0,0,...]这个数组,长度 length 为 2 (代码中简写为l) ,这个蛇头向上移动了一格,在地图上就是47这个位置变成了37。那么数组[45,46,47,0,0,....]就这样看,把第二个元素赋值给第一个元素,把第三个元素赋值给第二个元素,最后给第三个元素赋值为37,那么数组就会变为[46,47,37,0,...],(赋值个数由length决定)
int place ; //声明place变量为蛇头的位置
body[l]=place; //将蛇头的位置赋值给蛇身数组的对应位置
for(int i=0;i<l;i++){
body[i]=body[i+1];
}
其实这里我并不是很理解,如果将对body[l]的赋值放在for循环后,运行的时候出现很多问题。这样能行就酱紫吧,代码界不是有一句老话:能实现目的的代码就不要动它。(逃避可耻但很有用)。
代码以你不懂的方式运行了起来.jpg
第三步
这一步的实现比较简单。直接4个判断,见下方代码。并且将place变量修正为另一边。
如果要实现碰界结束直接将判断执行 设置一个 胜利失败标识符 ,这个标识符将判断是否失败
if(place<-1){ //向上越界
place += 100;
}
else if(place>100){ //向下越界
place -=100;
}
else if((place+1) %10 ==0 &move==4){ //向左越界
place += 10;
}
else if(place %10 ==0 &move==6){ //向右越界
place -=10;
}
第四步
实现食物的位置,简单的直接将食物的位置在地图上随机,只不过要判断食物的位置不能和蛇重合。代码如下:
board[ppl]=111; //食物的ascii码
if(place==ppl){ //蛇头和食物重合就给蛇长度+1
l +=1;
ppl=rand()%100; //设置食物位置
for(int i=0;i<l;i++){ //判断食物是否和蛇身位置重合,如果重合则再随机食物位置
if(ppl==body[i])
{
ppl=rand()%100;
}
}
}
第五步
实现游戏的胜利和失败。简单设置一个胜利失败标识符。
for(int i=1;i<l;i++){
if(place==body[i-1]){ //只要蛇头位置和蛇身第二位起相同,就判断为失败
lose=1;
}
}
if(l>99){
lose=2; //当长度l达到某值就判断为胜利。
}
然后是失败和胜利的结束界面
if(lose){ //判断是否失败
printf("you lose\n按R键重新开始,任意键退出");
int r=_getch(); //获取按键
if(r==114){ //r键的按键值为114
lose=0; //重现开始,初始化
int board[100]={0};
l=0;
}
else{
break; //否则退出
}
}
else if(lose==2){
printf("y=You Win!\n按R键重新开始,任意键退出");
int r=_getch();
if(r==114){
lose=0;
int board[100]={0};
l=0;
}
else{
break;
}
}
下面放上完整代码,才学不久啊,代码乱别骂俺QAQ。祝各位身体健康,永远不死。
#include<stdio.h>
#include<windows.h> //Sleep和system函数
#include<string.h> //memset 函数
#include<conio.h> //检测键盘函数getch
#include<stdlib.h> //rand函数
int main(){
int lose=0; //判断胜利失败的标识
int Fx; //下面判断方向用的
int move; //移动方向
int board[100]={0}; //设置地图,实现方法为长度为100的数组
memset(board,32,sizeof(board)); //初始化地图
int place=55; //初始化位置
int l=0; //贪吃蛇长度
int body[100]={0};
int lengthadd=0;
int i1;
int ppl;
ppl=rand()%100;
for(;;){
system("cls");
if(lose){ //判断是否失败
printf("you lose\n按R键重新开始,任意键退出");
int r=_getch();
if(r==114){
lose=0;
int board[100]={0};
l=0;
}
else{
break;
}
}
else if(lose==2){
printf("y=You Win!\n按R键重新开始,任意键退出");
int r=_getch();
if(r==114){
lose=0;
int board[100]={0};
l=0;
}
else{
break;
}
}
for(int i=0;i<100;i++){ //输出完整地图
if(i%10==0){
printf("\n%c ",board[i]);
}
else{
printf("%c ",board[i]);
}
}
printf("\nyour score:%d\n",l);
//printf("%d",place);
memset(board,32,sizeof(board));
Sleep(500);
if(_kbhit()){ //检测键盘按下
Fx=_getch();
switch (Fx)
{
case 119 : if(move==2){move=2;}else{move=8;};break; //根据返回值给move赋值,并且不能反方向
case 115 : if(move==8){move=8;}else{move=2;};break;
case 97 : if(move==6){move=6;}else{move=4;};break;
case 100 : if(move==4){move=4;}else{move=6;};break;
default :break;
}
}
switch (move)
{
case 8:place -=10;break; //通过字符的位置来表示蛇头位置
case 2:place +=10;break;
case 4:place -=1 ;break;
case 6:place +=1 ;break;
default:
break;
}
if(place<-1){ //实现越过边界时的穿越效果
place += 100;
}
else if(place>100){ //每当位置到边界且方向为穿越边界时,位置移动到另一边
place -=100;
}
else if((place+1) %10 ==0 &move==4){
place += 10;
}
else if(place %10 ==0 &move==6){
place -=10;
}
for(int i=1;i<l;i++){
if(place==body[i-1]){
lose=1;
}
}
body[l]=place;
for(int i=l;i>=0;i--){ //将蛇身的位置对应到棋盘上
board[body[i]]=79;
}
for(int i=0;i<l;i++){ //用另一个数组存储蛇身的位置
body[i]=body[i+1];
}
board[ppl]=111; //食物的ascii码 //设置食物位置
if(place==ppl){
l +=1;
ppl=rand()%100;
for(int i=0;i<l;i++){
if(ppl==body[i]){
ppl=rand()%100;
}
}
}
if(l>99){
lose=2;
}
}
}