对C语言小游戏——贪吃蛇的看法与个人改进
引言
之前在上C语言程序课的时候,老师有给我们放出有同学用C语言自制坦克大招小游戏的程序,打开了我学习C语言的大门。课程结束后,我对那时的坦克大战小游戏仍念念不忘,所以我在CSDN上查找更多C语言相关的游戏。正好找到了一个大佬自己写的贪吃蛇小游戏。
系统介绍
我们先进入程序看一看原系统的界面以及功能:
由于是作者的试做版本,界面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,侵权必删)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构