对C语言小游戏——贪吃蛇的看法与个人改进

  引言

   之前在上C语言程序课的时候,老师有给我们放出有同学用C语言自制坦克大招小游戏的程序,打开了我学习C语言的大门。课程结束后,我对那时的坦克大战小游戏仍念念不忘,所以我在CSDN上查找更多C语言相关的游戏。正好找到了一个大佬自己写的贪吃蛇小游戏。

  这里附上原帖链接:https://blog.csdn.net/qq_45882032/article/details/108943569?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167774029816800211512941%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167774029816800211512941&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-2-108943569-null-null.142^v73^wechat_v2,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=c%E8%AF%AD%E8%A8%80%E5%B0%8F%E6%B8%B8%E6%88%8F&spm=1018.2226.3001.4187

  系统介绍

  我们先进入程序看一看原系统的界面以及功能:

  

 

  由于是作者的试做版本,界面ui比较简洁,但功能分块清晰明朗,方便易懂。

 

  游戏介绍和游戏版本都是作者对游戏内容以及玩法背景等的介绍,这里就不过多展示了,感兴趣的可以去看看原帖复制源代码自己去看一看。我们这里重点观察他的一些游戏内的功能。

 

  

 

 

   在设置界面基本的难度修改以及自定义设置里面的对蛇的长度修改功能都较为齐全,但我测试时颜色和音效部分似乎并没有完全实现,等会我们可以移步作者的代码部分进一步观察。

  

 

 

   进入游戏界面后可以看到右侧简单的控制方法教学和食材剩余,以及左侧的墙壁、蛇身以及食材位置。基本上有了经典贪吃蛇的所有元素,可以作为一个优秀成品作品了。但是通过游玩后我仍发现了可以改进的地方。

  思考与建议

  对于我认为可改进的地方大概有以下几点:

  1.蛇身与墙壁颜色和形状一模一样,游玩时容易对墙壁不能形成一个“壁垒”的概念,从而使玩家容易“撞墙”而死。

  2.蛇头与蛇身没有明显区分,只能通过蛇的移动方向来判断蛇头,导致在暂停游戏时的画面我们无法准确分辨蛇头与蛇身的位置。

  3.颜色和音效功能尚未实现,可以考虑加入这些功能。

  4.控制键位过于单一,不便于那些习惯使用小键盘的玩家。

   接下来我们可以移步代码部分,看看作者编写程序的珍贵思路。

  源代码

复制代码
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<Windows.h>
  4 #include<time.h>
  5 #include<conio.h>
  6 constexpr auto maphigh = 28, mapwide = 84;
  7 struct virus
  8 {
  9     int x;
 10     int y;
 11 }virus;
 12 unsigned short snakesize = 50, speed = 300, len = 4;
 13 struct snake
 14 {
 15     int x[50];
 16     int y[50];
 17     int len;//蛇的长度
 18     int speed;//蛇的速度
 19 }snake;
 20 int key = 'w';//初始化方向
 21 void gotoxy(int x, int y);
 22 void drawmap();
 23 void keydown();
 24 void creatvirus();
 25 int snakestatus();
 26 void startgame();
 27 int menuselect();
 28 void goodbye();
 29 void introduce();
 30 void edition();
 31 int setup();
 32 void respect();
 33 int setup1();
 34 int modifydiffculty();
 35 void diffculty();
 36 int customize();
 37 void customize1();
 38 int main()
 39 {
 40     for (;;)
 41     {
 42         system("cls");//清屏
 43         switch (menuselect())
 44         {
 45         case 1://开始游戏
 46             startgame();
 47             break;
 48         case 2://介绍
 49             introduce();
 50             break;
 51         case 3://版本
 52             edition();
 53             break;
 54         case 4://设置
 55             setup1();
 56             break;
 57         case 5:
 58             goodbye();
 59             return 0;
 60         default:
 61             break;
 62         }
 63     }
 64 }
 65 void drawmap()//1.画地图
 66 {
 67     //⊙:病毒    █:蛇身(占用两个字符)
 68     srand((unsigned int)time(NULL));//随机病毒出现的位置
 69     int i, k;
 70     for (i = 0; i <= maphigh; i++)
 71     {
 72         gotoxy(0, i);
 73         printf("");//打印左边框
 74         gotoxy(mapwide, i);
 75         printf("");//打印右边框
 76     }
 77     for (i = 0; i <= mapwide; i += 2)//+=2因为 █占两个字节
 78     {
 79         gotoxy(i, 0);
 80         printf("");//打印下边框
 81         gotoxy(i, maphigh);
 82         printf("");//打印上边框
 83     }
 84     //画蛇
 85     snake.len = len;
 86     snake.speed = speed;
 87     //初始化蛇的位置
 88     snake.x[0] = mapwide / 2;
 89     snake.y[0] = maphigh / 2;//[0]为蛇头的位置
 90     //画蛇头
 91     gotoxy(snake.x[0], snake.y[0]);
 92     printf("");
 93     //画蛇身
 94     for (k = 1; k < snake.len; k++)
 95     {
 96         snake.x[k] = snake.x[k - 1] + 2;
 97         snake.y[k] = snake.y[k - 1];
 98         gotoxy(snake.x[k], snake.y[k]);
 99         printf("");
100     }
101     while (1)
102     {
103         virus.x = rand() % (mapwide - 4) + 2;//+2,+1这个与█所占的字符有关,长占两个字符宽1个
104         virus.y = rand() % (maphigh - 2) + 1;//画个图,显而易见
105         if (virus.x % 2 == 0)
106             break;
107     }
108     gotoxy(virus.x, virus.y);
109     printf("");
110     gotoxy(mapwide + 4, maphigh);
111     printf("⊙:%d", snakesize - snake.len);
112 }
113 void creatvirus()//2.产生病毒
114 {
115     if (snake.x[0] == virus.x && snake.y[0] == virus.y)
116     {
117         //printf("\a");//声音
118         snake.len++;
119         srand((unsigned)time(NULL));
120         while (1)
121         {
122             int flag = 1;
123             virus.x = rand() % (mapwide - 4) + 2;//+2,+1这个与█所占的字符有关,长占两个字符宽1个
124             virus.y = rand() % (maphigh - 2) + 1;//画个图,显而易见
125         //产生的病毒不能在蛇的身上
126             for (int k = 0; k < snake.len; k++)
127             {
128                 if (snake.x[k] == virus.x && snake.y[k] == virus.y)
129                 {
130                     flag = 0;//virus不合适的标志
131                     break;
132                 }
133             }
134             if (flag == 1 && virus.x % 2 == 0)//病毒位置合法且x坐标为偶数
135             {//x左右是以两个字节为单位运动的,若virus的x坐标为奇数,就不存在snake.x[k] == virus.x
136                 break;
137             }
138         }
139     }
140     gotoxy(virus.x, virus.y);
141     printf("");
142     gotoxy(mapwide + 8, 0);//将光标移走
143     printf("W");
144     gotoxy(mapwide + 6, 1);
145     printf("A S D \t进行控制");
146     gotoxy(mapwide + 4, maphigh);
147     printf("⊙:%d", snakesize - snake.len);
148 }
149 void keydown()//3.按键
150 {
151     int i, temp;
152     if (_kbhit()) //kbhit函数检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
153     {
154         fflush(stdin);// 清空输入缓冲区,通常是为了确保不影响后面的数据读取
155         temp = _getch();//getch:从控制台读取一个字符,但不显示在屏幕上
156         if ((temp == 'a' || temp == 'A') && (key != 'D' && key != 'd'))//解决了按反方向键蛇自杀的问题
157         {
158             key = temp;
159         }
160         if ((temp == 'w' || temp == 'W') && (key != 's' && key != 'S'))
161         {
162             key = temp;
163         }
164         if ((temp == 's' || temp == 'S') && (key != 'W' && key != 'w'))
165         {
166             key = temp;
167         }
168         if ((temp == 'D' || temp == 'd') && (key != 'a' && key != 'A'))
169         {
170             key = temp;
171         }
172     }
173     //擦除最后一节
174     gotoxy(snake.x[snake.len - 1], snake.y[snake.len - 1]);
175     printf("  ");
176     for (i = snake.len - 1; i > 0; i--)
177     {
178         snake.x[i] = snake.x[i - 1];//以前1的位置现在变成了2,0变成了1
179         snake.y[i] = snake.y[i - 1];
180     }
181     switch (key)//最后一节已经擦出,前移后0,1位置重合,左上角坐标为0,0
182     {
183     case'w':
184     case'W':
185         snake.y[0]--;
186         break;
187     case's':
188     case'S':
189         snake.y[0]++;
190         break;
191     case'a':
192     case'A':
193         snake.x[0] -= 2;
194         break;
195     case'd':
196     case'D':
197         snake.x[0] += 2;
198         break;
199     }
200     gotoxy(snake.x[0], snake.y[0]);
201     printf("");
202     gotoxy(mapwide + 2, 0);//将光标移走
203 }
204 int snakestatus()//4.蛇的状态
205 {
206     if ((snake.x[0] == 0 || snake.x[0] == mapwide) || (snake.y[0] == 0 || snake.y[0] == maphigh))
207         return 0;
208     for (int k = 1; k < snake.len; k++)
209     {
210         if (snake.x[0] == snake.x[k] && snake.y[0] == snake.y[k])
211             return 0;
212     }
213     return 1;
214 }
215 void gotoxy(int x, int y)
216 {//1.找到控制台这个窗口
217     HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
218     /*HANDLE为句柄  ↑得到(输出窗口的)句柄。
219     Windows是一个以虚拟内存为基础的操作系统,很多时候,
220     进程的代码和数据并不全部装入内存,进程的某一段装入内存后,
221     还可能被换出到外存,当再次需要时,再装入内存。两次装入的地址绝大多数情况下是不一样的。
222     也就是说,同一对象在内存中的地址会变化。那么,程序怎么才能准确地访问到对象呢?为了解决这个问题,Windows引入了句柄。
223     数值上,是一个32位无符号整型值(32位系统下);逻辑上,相当于指针的指针;形象理解上,是Windows中各个对象的一个唯一的、固定不变的ID;
224     作用上,Windows使用句柄来标识诸如窗口、位图、画笔等对象,并通过句柄找到这些对象。*/
225     //2.设置光标
226     COORD coord;
227     /*COORD 为Windows.h中自带函数原型大体为struct _coord{short x;short y;}coord;*/
228     coord.X = x;
229     coord.Y = y;
230     //4.同步到控制台SetConsoleCursorPosition
231     SetConsoleCursorPosition(handle, coord);//定位到handle这个窗口,把光标打在coord坐标
232 }
233 void startgame()
234 {
235     system("cls");
236     drawmap();
237     while (1)
238     {
239         creatvirus();
240         keydown();
241         Sleep(snake.speed);//void sleep(int seconds)自带函数参数 seconds 为要暂停的毫秒数。
242         if (!snakestatus())//判断死亡时snakestaus为0,
243         {
244             gotoxy(mapwide / 2, maphigh / 2);
245             printf("Game Over");
246             getchar();
247             getchar();
248             break;
249         }
250         if (snake.len == snakesize)
251         {
252             gotoxy(mapwide / 3, maphigh / 2);
253             printf("恭喜您消灭了全部病毒,胜利(^-^)V");
254             getchar();
255             respect();
256             break;
257         }
258     }
259 }
260 int menuselect()
261 {
262     char number;
263     int a;
264     printf("\n\n\t\t\t\t\t\t1.开始游戏\n");
265     printf("\n\t\t\t\t\t\t2.游戏介绍\n");
266     printf("\n\t\t\t\t\t\t3.游戏版本\n");
267     printf("\n\t\t\t\t\t\t4.设置\n");
268     printf("\n\t\t\t\t\t\t5.退出游戏\n");
269     printf("\n\t\t\t\t\t\t请选择(数字)");
270     while (1)
271     {
272         number = getchar();
273         a = (int)number - 48;
274         if (number <= '5' && number >= '1')
275             return a;
276         printf("\n\t\t\t\t\t\t\t      ");
277     }
278 }
279 void goodbye()
280 {
281     system("cls");
282     SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);//设置红色
283     gotoxy(0, 12);
284     printf("\t\t\t\t\t\t谢谢使用!再见!\n");
285     getchar();
286     getchar();
287 }
288 void respect()
289 {
290     system("cls");
291     gotoxy(0, 4);
292     printf("\t\t*在此向全国的医护人员表示敬意*\n\n");
293     Sleep(1000);
294     printf("\t\t*感谢他们的默默付出*\n\n");
295     Sleep(1000);
296     printf("\t\t*感谢他们对抗击疫情做出的贡献*\n\n");
297     Sleep(1000);
298     printf("\t\t*此致*\n\n");
299     printf("\t\t*                      敬礼 *\n\n");
300     Sleep(2000);
301     getchar();
302 }
303 void introduce()
304 {
305     system("cls");
306     gotoxy(0, 4);
307     printf("\t\t\t\t        游戏规则           \n\n");
308     printf("\t\t\t\t2020年新冠病毒肆虐,威胁着人类\n\n");
309     printf("\t\t\t\t玩家将控制蛇消灭随机出现的病毒⊙\n\n");
310     printf("\t\t\t\t消灭所有病毒即可获得胜利。\n\n");
311     printf("\n\n\n\n\n");
312     printf("\t\t\t\t\tEnter返回主菜单");
313     getchar();
314     getchar();
315 }
316 void edition()
317 {
318     system("cls");
319     gotoxy(0, 4);
320     printf("\t\t\t\t*********************************************\n\n");
321     printf("\t\t\t\t*               欢         迎               *\n\n");
322     printf("\t\t\t\t*                版本号: 1.2               *\n\n");
323     printf("\t\t\t\t*        更新:1.修复了反方向自杀的问题     *\n\n");
324     printf("\t\t\t\t*              2.修复了蛇吃墙的问题         *\n\n");
325     printf("\t\t\t\t*              3.新增了菜单与设置功能       *\n\n");
326     printf("\t\t\t\t*              4.修改了部分整形变量节省空间 *\n\n");
327     printf("\t\t\t\t*********************************************\n\n");
328     printf("\t\t\t\t\tEnter返回主菜单");
329     getchar();
330     getchar();
331 }
332 int setup()
333 {
334     //system("color 6f"); //第一个为背景色,第二个为字体颜色
335    /*0 = 黑色       8 = 灰色
336     1 = 蓝色       9 = 淡蓝色
337     2 = 绿色       A = 淡绿色
338     3 = 湖蓝色     B = 淡浅绿色
339     4 = 红色       C = 淡红色
340     5 = 紫色       D = 淡紫色
341     6 = 黄色       E = 淡黄色
342     7 = 白色       F = 亮白色*/
343     char s;
344     int a;
345     system("cls");
346     printf("\n\n\t\t\t\t\t\t1.修改难度\n");
347     printf("\n\t\t\t\t\t\t2.自义定设置\n");
348     printf("\n\t\t\t\t\t\t3.颜色设置\n");
349     printf("\n\t\t\t\t\t\t4.音效设置\n");
350     printf("\n\t\t\t\t\t\t5.返回\n");
351     printf("\n\t\t\t\t\t\t请选择(数字)");
352     while (1)
353     {
354         s = getchar();
355         a = (int)s - 48;
356         if (s <= '5' && s >= '1')
357             return a;
358         printf("\n\t\t\t\t\t\t\t      ");
359     }
360 }
361 int setup1()
362 {
363     switch (setup())
364     {
365     case 1:
366         diffculty();
367         break;
368     case 2:
369         customize1();
370         break;
371     default:
372         break;
373     }
374     return 0;
375 }
376 int modifydiffculty()
377 {
378     char s;
379     int a;
380     system("cls");
381     printf("\n\n\t\t\t\t\t\t1.简单\n");
382     printf("\n\t\t\t\t\t\t2.普通\n");
383     printf("\n\t\t\t\t\t\t3.困难\n");
384     printf("\n\t\t\t\t\t\t4.修罗地狱\n");
385     printf("\n\t\t\t\t\t\t请选择(数字)");
386     while (1)
387     {
388         s = getchar();
389         a = (int)s - 48;
390         if (s <= '4' && s >= '1')
391             return a;
392         printf("\n\t\t\t\t\t\t\t      ");
393     }
394 }
395 void diffculty()
396 {
397     switch (modifydiffculty())
398     {
399     case 1:
400         len = 4;
401         speed = 500;
402         snakesize = 10;
403         system("cls");
404         gotoxy(8, 8);
405         printf("\t\t\t\t\t\t 修改成功!\n");
406         printf("\t\t\t\t\t\t \n");
407         system("pause");
408         break;
409     case 2:
410         len = 4;
411         speed = 300;
412         snakesize = 25;
413         system("cls");
414         gotoxy(8, 8);
415         printf("\t\t\t\t\t\t 修改成功!\n");
416         printf("\t\t\t\t\t\t \n");
417         system("pause");
418         break;
419     case 3:
420         len = 4;
421         speed = 70;
422         snakesize = 50;
423         system("cls");
424         gotoxy(8, 8);
425         printf("\t\t\t\t\t\t 修改成功!\n");
426         printf("\t\t\t\t\t\t \n");
427         system("pause");
428         break;
429     case 4:
430         len = 4;
431         speed = 25;
432         snakesize = 70;
433         system("cls");
434         gotoxy(8, 8);
435         printf("\t\t\t\t\t\t 修改成功!\n");
436         printf("\t\t\t\t\t\t \n");
437         system("pause");
438         break;
439     default:
440         break;
441     }
442 }
443 int customize()
444 {
445     char s;
446     int a;
447     system("cls");
448     printf("\n\n\t\t\t\t\t\t1.自定义速度\n");
449     printf("\n\t\t\t\t\t\t2.自定义初始长度\n");
450     printf("\n\t\t\t\t\t\t请选择(数字)");
451     while (1)
452     {
453         s = getchar();
454         a = (int)s - 48;
455         if (s <= '2' && s >= '1')
456             return a;
457         printf("\n\t\t\t\t\t\t\t      ");
458     }
459 }
460 void customize1()
461 {
462     int s;
463     switch (customize())
464     {
465     case 1://自定义速度
466         system("cls");
467         gotoxy(8, 8);
468         printf("\t\t\t\t请输入速度(1-999)");
469         scanf_s("%d", &s);
470         speed = (1000 - s);
471         break;
472     case 2:
473         system("cls");
474         gotoxy(8, 8);
475         printf("\t\t\t\t请输入初始长度:");
476         scanf_s("%d", &s);
477         len = s;
478         break;
479     default:
480         break;
481     }
482 }
复制代码

   可以看到在对蛇身体的符号与墙体符号是同一个符号,在此我稍作修改,将蛇身与墙体作为区分,以下是我修改后的代码:

 

    //画蛇身
    for (k = 1; k < snake.len; k++)
    {
        snake.x[k] = snake.x[k - 1] + 2;
        snake.y[k] = snake.y[k - 1];
        gotoxy(snake.x[k], snake.y[k]);
        printf("");
    }

 

  接下来我们运行一下程序看看效果

 

  可以看到修改后的蛇身与墙体对比更为明显,视觉上更有分离感。但蛇头在哪的问题还没有完全解决,于是我对蛇头的代码也做了部分修改:

复制代码
//画蛇头
gotoxy(snake.x[0], snake.y[0]);
printf("");

void keydown()//3.按键
{
    int i, temp;
    if (_kbhit()) //kbhit函数检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
    {
        fflush(stdin);// 清空输入缓冲区,通常是为了确保不影响后面的数据读取
        temp = _getch();//getch:从控制台读取一个字符,但不显示在屏幕上
        if ((temp == 'a' || temp == 'A') && (key != 'D' && key != 'd'))//解决了按反方向键蛇自杀的问题
        {
            key = temp;
        }
        if ((temp == 'w' || temp == 'W') && (key != 's' && key != 'S'))
        {
            key = temp;
        }
        if ((temp == 's' || temp == 'S') && (key != 'W' && key != 'w'))
        {
            key = temp;
        }
        if ((temp == 'D' || temp == 'd') && (key != 'a' && key != 'A'))
        {
            key = temp;
        }
    }
    //擦除最后一节
    gotoxy(snake.x[snake.len - 1], snake.y[snake.len - 1]);
    printf("  ");
    for (i = snake.len - 1; i > 0; i--)
    {
        snake.x[i] = snake.x[i - 1];//以前1的位置现在变成了2,0变成了1
        snake.y[i] = snake.y[i - 1];
    }
    switch (key)//最后一节已经擦出,前移后0,1位置重合,左上角坐标为0,0
    {
    case'w':
    case'W':
        snake.y[0]--;
        break;
    case's':
    case'S':
        snake.y[0]++;
        break;
    case'a':
    case'A':
        snake.x[0] -= 2;
        break;
    case'd':
    case'D':
        snake.x[0] += 2;
        break;
    }
    gotoxy(snake.x[0], snake.y[0]);
    printf("");
    gotoxy(mapwide + 2, 0);//将光标移走
}
复制代码

  接下来我们继续运行下程序

 

  现在我们能非常清楚的看到蛇头的位置,在游戏暂停是能更好的制定策略,使玩家游玩体验有部分提升。

  最后为了解决部分玩家习惯用数字小键盘来玩游戏的问题,我对控制的代码也进行了部分修改:

复制代码
void keydown()//3.按键
{
    int i, temp;
    if (_kbhit()) //kbhit函数检查当前是否有键盘输入,若有则返回一个非0值,否则返回0
    {
        fflush(stdin);// 清空输入缓冲区,通常是为了确保不影响后面的数据读取
        temp = _getch();//getch:从控制台读取一个字符,但不显示在屏幕上
        if ((temp == 'a' || temp == 'A'|| temp == '4') && (key != 'D' && key != 'd' && key != '6'))//解决了按反方向键蛇自杀的问题
        {
            key = temp;
        }
        if ((temp == 'w' || temp == 'W'|| temp == '8') && (key != 's' && key != 'S' && key != '2'))
        {
            key = temp;
        }
        if ((temp == 's' || temp == 'S'|| temp == '2') && (key != 'W' && key != 'w' && key != '8'))
        {
            key = temp;
        }
        if ((temp == 'D' || temp == 'd'|| temp == '6') && (key != 'a' && key != 'A' && key != '4'))
        {
            key = temp;
        }
    }
    //擦除最后一节
    gotoxy(snake.x[snake.len - 1], snake.y[snake.len - 1]);
    printf("  ");
    for (i = snake.len - 1; i > 0; i--)
    {
        snake.x[i] = snake.x[i - 1];//以前1的位置现在变成了2,0变成了1
        snake.y[i] = snake.y[i - 1];
    }
    switch (key)//最后一节已经擦出,前移后0,1位置重合,左上角坐标为0,0
    {
    case'w':
    case'W':
    case'8':
        snake.y[0]--;
        break;
    case's':
    case'S':
    case'2':
        snake.y[0]++;
        break;
    case'a':
    case'A':
    case'4':
        snake.x[0] -= 2;
        break;
    case'd':
    case'D':
    case'6':
        snake.x[0] += 2;
        break;
    }
    gotoxy(snake.x[0], snake.y[0]);
    printf("");
    gotoxy(mapwide + 2, 0);//将光标移走
}
复制代码

  最后我们来测试一下能否成功运行

 

 

   完美运行!

  以上就是我对该贪吃蛇游戏的小优化,目前音效和颜色功能由于修改时遇到bug,暂时没有完善这两个功能,如果有感兴趣的朋友也可以提出你们的修改意见私信给我哦!

(转自CSDN,侵权必删)

 

posted @   RakkaYukino  阅读(163)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示