本博客的主要目的是學習<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,就消失