C 語言實現貪喫蛇

本博客的主要目的是學習<windows.h><conio.h>頭文件 和 各種有趣的函數,順便觀膜真正簡潔與高級的源代碼

參考po主:RainbowRoad1,非常感謝po主提供了源碼

貪喫蛇源代碼(VS2019運行)

#include<windows.h>
#include<stdlib.h>
#include<conio.h>
#include<stdio.h>
#include<time.h>
#define Width 25///可以宏定義地圖大小(奇數,否則牆會不整齊)
int main()
{
	srand((unsigned)time(NULL));//初始化隨機數種子
	do///重新開始,直接套一個循環
	{
		int hX = Width / 5, hY = Width / 5, len = 4, i = 0, map[Width * Width] = { 0 };//頭座標,蛇長,循環變量,地圖(-1:食物;0:空白;>0:蛇身)
		for (i = 0; i < Width; i += 2)///地圖元素:-2:牆
			map[i] = map[Width * Width - 1 - i] = map[i * Width] = map[i * Width + Width - 1] = -2;//使四周的牆都隔一格分佈
		char c = 'd', cl = 3, deaw[Width * Width * 2 + 1] = { 0 };//初始方向,輸入緩存,繪製緩存
		sprintf_s(deaw, 32, "mode con: cols=%d lines=%d", Width * 2, Width);
		system(deaw);//修改控制檯窗口大小
		for (int num = 3; num; num--)///生成多個食物,num控制數量
		{
			do i = rand() % (Width * Width);
			while (map[i]);
			map[i] = -1;
		}
		for (system("title 得分:0"); 1; Sleep(100))///初始化計分板,延遲
		{
			if (_kbhit() && (cl = _getch()))//判斷是否輸入
				while (cl == ' ')
				{
					switch (cl)
					{
					case 'a':case 'A':if (c != 'd')c = 'a'; break;//判斷與原方向是否衝突
					case 'd':case 'D':if (c != 'a')c = 'd'; break;
					case 's':case 'S':if (c != 'w')c = 's'; break;
					case 'w':case 'W':if (c != 's')c = 'w'; break;
					case ' ':cl = _getch(); break;///空格暫停
					case 27:exit(0); break;///Esc退出
					}
				}
			switch (c)//允許穿牆
			{
			case 'a':hX -= (hX > 0 ? 1 : 1 - Width); break;//更新頭座標
			case 'd':hX += (hX < Width - 1 ? 1 : 1 - Width); break;
			case 's':hY += (hY < Width - 1 ? 1 : 1 - Width); break;
			case 'w':hY -= (hY > 0 ? 1 : 1 - Width); break;
			}
			if (map[hY * Width + hX] > 1 || map[hY * Width + hX] == -2) break;//判斷是否喫到自己 或 是否撞牆
			if (map[hY * Width + hX] == -1)//判斷是否喫到食物
			{
				len++;
				do i = rand() % (Width * Width);//刷新食物
				while (map[i]);//防止食物位置覆蓋蛇
				map[i] = -1;
				sprintf_s(deaw, 32, "title 得分:%d", len - 4);///計分板
				system(deaw);
			}
			else
				for (i = 0; i < Width * Width; i++)//全部蛇身值-1
					if (map[i] > 0)map[i] -= 1;
			map[hY * Width + hX] = len;//蛇頭賦值
			for (i = 0; i < Width * Width * 2; i++)//更新繪製緩存
			{
				if (map[i / 2] == 0)deaw[i] = ' ';
				else if (map[i / 2] > 0)deaw[i] = (i & 1) ? ')' : '(';
				else if (map[i / 2] == -2)deaw[i] = (i & 1) ? ']' : '[';///牆壁
				else deaw[i] = '0';//食物
			}
			system("cls");//清屏
			printf(deaw);//打印(能去閃爍)
		}
		sprintf_s(deaw, 48, "title GameOver!得分:%d 按空格鍵重新開始", len - 4);
		system(deaw);
	} while (_getch() == ' ');///空格鍵繼續
}

講解函數

  • srand()函數(頭文件 <stdlib.h> )
    衆所周知,rand() 函數不需要參數,它會返回 0 到 RAND_MAX(一般是32767)之間的任意的整數,但它也是僞隨機數,只是週期特別長,所以在一定範圍內可以看成隨機的,若想要生成隨機數,加上srand() 函數即可,它相當於初始化隨機數發生器,舉例如下:
srand(1)
int temp = rand()  //1314

srand(2)
int temp = rand()  //8848

二者生成的數值是不同的,利用這一特性,我們加以利用time()函數 (頭文件<time.h>)
time()函數是獲取當前系統時間的函數,以秒作單位,我們把它嵌入到srand()函數中

srand(time(0))  //寫 time(0)、time(NULL)都是一樣的
int temp = rand()  //2333

現在生成的就是真正的隨機數

補充:若想生成 0 - 1 內的隨機數可以寫(double)rand() / RAND_MAX

  • sprintf_s()函數(頭文件 <stdio.h> )
    其函數功能是將數據格式化輸出到字符串,括號內包括四個參數,第一個參數是存儲的位置,第二個參數是要存儲的大小,第三個是數據格式化,第四參數是存入的位置,返回值是已存入的空間大小
舉個例子:

char buffer[200], s[] = "computer", c = 'l';
int i = 35, j;
float fp = 1.7320534f;
j = sprintf_s( buffer, 200, " String: %s\n", s );
j += sprintf_s( buffer + j, 200 - j, " Character: %c\n", c );
j += sprintf_s( buffer + j, 200 - j, " Integer: %d\n", i );
j += sprintf_s( buffer + j, 200 - j, " Real: %f\n", fp );
printf_s( "Output:\n%s\ncharacter count = %d\n", buffer, j );

打印結果如下:
Output:
String: computer
Character: l
Integer: 35
Real: 1.732053

character count = 61
  • system()函數(頭文件 <windows.h> )
    執行 shell 命令,也就是向操作系統發送一條指令,舉例如下:
system("mode con: cols=60 lines=30");

代表調整系統控制檯顯示的寬度和高度,寬度爲60個字符,高度爲30個字符

而源代碼出現的:
sprintf_s(deaw, 32, "mode con: cols=%d lines=%d", Width * 2, Width);
system(deaw);

//意思都一樣,只是分開寫了

至於 sprintf_s 後面的兩個參數比是 2 :1 是爲了系統控制檯爲正方形,第二參數是 32 可能是因爲操作系統的大小爲 32 吧(這個我不確定)

system("title 得分:0")

顯示系統控制檯的標題

system("cls");

同樣的道理,清屏

  • Sleep()函數(頭文件 <windows.h> )
舉例:
Sleep(1000)  //表示休息一秒

在本代碼中可以理解爲貪喫蛇移動速度(屏幕刷新頻率)
  • kbhit()函數 和 getch()函數(頭文件 <conio.h> )
    kbhit() 函數用於檢測用戶是否有按下某鍵(鍵盤任意一鍵),當用戶按下一次按鍵時返回 1,否則返回 0;重點在於,程序運行到 kbhit() 語句時程序不會暫停,而會繼續運行,故當多次使用 kbhit() 時,程序會不停接收返回值
    getch() 函數用於返回從鍵盤上讀取到的字符,而且當檢測到用戶輸入一個字符後就會繼續程序,不需要按回車鍵,並且字符不會在屏幕上顯示
    之所以代碼中寫的是 _getch()_kbhit(),是因爲 VS2019 不支持傳統函數(部分),會認爲其不安全,加個下劃線就好了

分析代碼

一、空格暫停是如何實現的?

以下部分源碼說明:

if (_kbhit() && (cl = _getch()))
	while (cl == ' ')
	{
		switch (cl)
		{
		case 'a':case 'A':if (c != 'd')c = 'a'; break;
		case 'd':case 'D':if (c != 'a')c = 'd'; break;
		case 's':case 'S':if (c != 'w')c = 's'; break;
		case 'w':case 'W':if (c != 's')c = 'w'; break;
		case ' ':cl = _getch(); break;
		case 27:exit(0); break;
		}
	}

一般情況下,程序遇到 _getch() 函數是要暫停的,但這個 if 從句中還包含 _kbhit() 函數,故在沒有輸入鍵位的情況下依然會執行本語句,直到出現空格鍵,程序暫停,進入 case 中,之後若輸入空格或別的鍵位纔會繼續,若輸入 a w s d 中的其中一個鍵位則會直接向其方向運動

二、如何實現使貪喫蛇蛇尾消失?

本代碼並沒用運用列表,蛇尾是隨着循環自動消失的,以下部分源碼說明:

for (i = 0; i < Width * Width; i++)
	if (map[i] > 0)map[i] -= 1;

先將所有蛇身減去 1

map[hY * Width + hX] = len;
if (map[i / 2] == 0)deaw[i] = ' ';

將蛇頭賦值爲身體長度,再將蛇身爲 0 刪去,意思是蛇身的每個部分會隨着循環逐漸減小,直到最後爲 0,就消失

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章