本博客的主要目的是學習 Windows API 和 各種有趣的函數,順便觀膜多函數分佈的函數的源代碼
參考po主:mayali123,非常感謝po主提供了源碼
貪喫蛇源代碼(VS2019運行)(建議函數從下往上看)
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<conio.h>
#define up 'w'
#define down 's'
#define left 'a'
#define right 'd'
#define stop 'p'
#define add_speed 'j'
#define down_speed 'l'
constexpr auto i_min = 35; //用於遊戲開始邊框的確定
constexpr auto i_max = 85;
constexpr auto j_min = 17;
constexpr auto j_max = 23;
struct snake {
int x;
int y;
struct snake* next; //爲使用鏈表做準備
}; //蛇的結構體
const HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //標準輸出的句柄
struct snake* tail, * head, * food_1;
int i = 0; //用來保存成績
int max; //成績的最大值
int sleeptime = 200; //通過sleeptime來控制速度
char click = 1; //保存鍵盤的輸入
int rand_color = 3; //保存產生的隨機數,將食物的顏色變成隨機的
int last_color = 3; //保存上一次產生隨機數
void color(int i); //通過這個函數來改變字體的顏色
void gotoxy(int x, int y); //將光標移到(x,y)處
void start_play(); //開始遊戲
void square(int i_min1, int i_max1, int j_min1, int j_max1); //產生一個矩形框
void game_over(int k); //遊戲結束
void make_map(); //加載地圖
void text(); //加載文本
void init_snake(); //初始化蛇的身體
void food(); //初始化食物
void file_scanf(); //讀入save.txt保存成績的最大值
void file_printf(); //將最大值寫入save.txt
void gotodelete(int i, int y); //清空打出來的蛇身體
void change_body(int a, int b); //改變蛇身體位置
void gotoprint(int x, int y); //打印蛇
void MovingBody(); //移動蛇
void ClickControl(); //得到鍵盤的輸入
void eating(); //當蛇喫到東西時
int judge(); //判斷蛇是否撞到東西
void refresh(); //刷新成績和速度
void file_printf()
{
FILE* fp;
fopen_s(&fp, "./save.txt", "w+"); //這個會覆蓋之前的值
if (fp != NULL)
fprintf(fp, "%d", i);
fclose(fp);
}
void game_over(int k)
{
int j;
file_scanf(); //讀入save.txt保存成績的最大值
system("cls");
square(20, 100, 5, 20);
gotoxy(55, 8);
color(7);
printf("(>﹏<)\n");
color(6);
gotoxy(44, 11);
if (k == 0) //判斷遊戲結束的原因
printf("好可惜呀,你撞牆了,遊戲結束\n");
else
printf("好可惜呀,你撞到自己了,遊戲結束\n");
gotoxy(54, 14);
color(10);
printf("你的得分是%d", i);
gotoxy(38, 17);
if (i < max) //判斷成績是否大於最大值
printf("(^_^)繼續努力吧,你離最高分還差%d", max - i);
else
{
printf("(^_^)太厲害了吧,你刷新了記錄,現在最高分是%d", i);
file_printf(); //成績大於最大值,將成績寫入文件
}
gotoxy(40, 22);
printf("[1]在來一局\t\t\t[2]跑路了");
gotoxy(52, 24);
printf("請選擇[1 2]:[ ]");
gotoxy(65, 24);
scanf_s("%d", &j);
switch (j) //對選擇進行判斷
{
case 1:
system("cls");
i = 0; //爲重玩做準備,將成績和速度變成初始值
sleeptime = 200;
make_map();
ClickControl();
break;
case 2:
gotoxy(65, 25);
printf("正在退出遊戲...");
Sleep(500);
system("cls");
exit(0);
}
}
void change_body(int a, int b)
{
snake* p;
p = head;
int x, y, x1, y1;
x = a;
y = b;
p = p->next;
while (p->next != NULL) //用這將head後面的移動到他前一個的位置上
{
x1 = p->x;
y1 = p->y;
p->x = x;
p->y = y;
x = x1;
y = y1;
p = p->next;
}
}
void eating()
{
snake* now, * p;
if (food_1->x == head->x && food_1->y == head->y) //判斷蛇是否喫到食物
{
food();
now = (snake*)malloc(sizeof(snake));
p = head;
while (p->next->next != NULL)
p = p->next;
now->next = p->next;
p->next = now; //加一條尾巴
i += 10; //當喫到東西時,成績+10,速度增加
if (sleeptime >= 40)
sleeptime -= 10;
refresh(); //對成績和速度進行刷新
}
}
void gotodelete(int i, int y)
{
gotoxy(i, y);
printf(" ");
}
void MovingBody()
{
int a = head->x, b = head->y;
snake* p = head;
while (p->next != NULL) //通過先清空後打印實現動畫效果
{
gotodelete(p->x, p->y);
p = p->next;
}
switch (click) //對按下的鍵盤進行判斷
{
case up:
head->y -= 1;
change_body(a, b);
break;
case down:
head->y += 1;
change_body(a, b);
break;
case left:
head->x -= 2;
change_body(a, b);
break;
case right:
head->x += 2;
change_body(a, b);
break;
case stop:
break;
case add_speed: //通過改變sleeptime,達到加速
if (sleeptime >= 50)
{
sleeptime -= 10;
i += 5;
}
refresh();
break;
case down_speed:
if (sleeptime <= 200)
{
sleeptime += 10;
i -= 5;
}
refresh();
break;
}
p = head;
eating(); //看蛇是否喫到東西
while (p->next != NULL) //打印蛇
{
gotoprint(p->x, p->y);
p = p->next;
}
p = head;
gotoxy(0, 28); //讓輸入的光標在(0,28)處
Sleep(sleeptime);
}
int judge()
{
int i_min2 = 0, i_max2 = 56, j_min2 = 0, j_max2 = 26;
snake* q;
q = head->next;
while (q->next != NULL)
{
if (head->x == q->x && head->y == q->y)
{
game_over(1); //1代表撞到自己了
return 1;
}
q = q->next;
}
if (head->x == i_min2 || head->x == i_max2 || head->y == j_min2 || head->y == j_max2)
{
game_over(0); //2代表撞到牆了
return 1;
}
else
return 0;
}
void ClickControl()
{
click = 'p'; //爲重玩做準備
while (judge() == 0) //判斷是否撞到東西
{
if (_kbhit()) //判斷是否有鍵盤按下
{
click = _getch();
}
MovingBody();
}
}
void food()
{
srand((unsigned)time(NULL));
struct snake* q;
food_1 = (snake*)malloc(sizeof(snake));
do
{
food_1->x = rand() % 52 + 2;
} while ((food_1->x % 2) != 0); //食物隨機出現,食物的x座標在2~53,且爲偶數
food_1->y = rand() % 24 + 1;
q = head;
while (q != NULL)
{
if (q->x == food_1->x && q->y == food_1->y) //判斷食物是不是在蛇的身體上
{
free(food_1);
food();
}
q = q->next;
}
last_color = rand_color; //通過這個來實現蛇喫個什麼顏色的食物就變什麼顏色
srand((unsigned)time(NULL));
color(rand_color = rand() % 10 + 1); //隨機產生1~10的顏色
gotoxy(food_1->x, food_1->y); //打印食物
printf("●");
}
void gotoprint(int x, int y)
{
color(last_color);
gotoxy(x, y);
printf("◆");
}
void init_snake()
{
int i, k, j;
srand((unsigned)time(NULL)); //重新播種,有利於產生隨機數
tail = (snake*)malloc(sizeof(snake));
do
{
k = rand() % 46 + 2;
} while ((k % 2) != 0); //蛇頭x座標在2~47,且爲偶數
j = rand() % 22 + 1; //蛇頭y座標在2~47
tail->x = k;
tail->y = j; //實現貪喫蛇的隨機出現
tail->next = NULL;
for (i = 1; i < 5; i++) //產生四個蛇身體
{
head = (snake*)malloc(sizeof(snake));
head->next = tail;
head->x = k + i * 2;
head->y = j;
tail = head;
}
while (tail->next != NULL) //打印蛇
{
gotoprint(tail->x, tail->y);
tail = tail->next;
}
food(); //加載食物
}
void square(int i_min1, int i_max1, int j_min1, int j_max1)
{
int i, j;
color(3);
for (j = j_min1; j <= j_max1; j++)
{
for (i = i_min1; i <= i_max1; i++)
{
gotoxy(i, j);
if (j == j_min1 || j == j_max1)
printf("-");
else if (i == i_min1 || i == i_max1)
printf("|");
}
printf("\n");
}
}
void refresh()
{
color(i / 10);
gotoxy(76, 10);
printf(" ☆當前得分:%d(^_^;)☆", i);
gotoxy(76, 12);
printf(" ☆當前速度:%d(⌒_⌒;)☆", (21 - sleeptime / 10));
}
void file_scanf()
{
FILE* fp;
fopen_s(&fp, "./save.txt", "r+");
if (fp != NULL) //判斷文件打開是否成功
fscanf_s(fp, "%d", &max);
fclose(fp);
}
void text()
{
file_scanf();
color(3);
gotoxy(60, 4);
printf("\t\t\t\t( ^ω^)");
gotoxy(76, 7);
printf(" ☆最高分紀錄:%d☆", max); //打印最高分
color(6);
refresh(); //因爲成績和速度會變化所以要在其改變時刷新
gotoxy(76, 14);
color(9);
printf("\t 小提示"); //打印一些提示
square(65, 115, 15, 28);
gotoxy(66, 17);
printf("tip1: 不能撞牆,不能咬到自己");
gotoxy(66, 19);
printf("tip2: 用英文的w.s.a.d分別控制蛇的移動");
gotoxy(66, 21);
printf("tip3: 按空格鍵暫停遊戲,需要再次按w.s.a.d纔可以動");
gotoxy(66, 23);
printf("top4: 按j加速l降速,需要再次按w.s.a.d纔可以動");
gotoxy(66, 25);
printf("top5: 開始時蛇的頭在右邊");
gotoxy(66, 27);
printf("由於技術技術原因本遊戲存在着一些bug請見諒");
gotoxy(66, 29);
}
void make_map()
{
color(3);
int i, j, i_min2 = 0, i_max2 = 56, j_min2 = 0, j_max2 = 26;
for (j = j_min2; j <= j_max2; j++) //加載地圖
{
for (i = i_min2; i <= i_max2; i += 2)
{
gotoxy(i, j);
if (j == j_min2 || j == j_max2)
printf("■");
else
{
if (i == i_min2 || i == i_max2)
printf("■");
}
}
printf("\n");
}
text(); //加載文字
init_snake(); //初始化蛇身
}
void color(int i) //3 爲藍色 4 爲紅色
{
SetConsoleTextAttribute(handle, i); //改變字體的顏色
}
void gotoxy(int x, int y)
{
COORD c; //COORD是一個結構體
c.X = x;
c.Y = y;
SetConsoleCursorPosition(handle, c); //設置光標的位置
}
void start_play()
{
int i, j;
gotoxy(i_min + 20, j_min - 2); //加載遊戲開始的界面
color(4);
printf("貪 喫 蛇 遊 戲");
gotoxy(i_min + 5, j_min + 2);
printf("[1]開始遊戲\t\t [2]遊戲說明");
gotoxy(i_min + 5, j_min + 4);
printf("[3]退出遊戲");
color(3);
square(i_min, i_max, j_min, j_max); //打出矩形框
gotoxy(i_min, j_max + 1);
printf("請選擇[1 2 3]:[ ]");
gotoxy(i_min + 15, j_max + 1);
scanf_s("%d", &i);
switch (i) //對選擇進行判斷
{
case 1:
system("cls"); //清屏
make_map();
ClickControl();
break;
case 2:
system("cls");
text();
gotoxy(20, 12);
printf("[1]返回遊戲 [2]退出遊戲");
gotoxy(20, 14);
printf("請選擇[1 2]:[ ]");
gotoxy(33, 14);
scanf_s("%d", &i);
if (i == 1)
{
system("cls");
make_map();
ClickControl();
break;
} //如果i爲2,則進入case3中
case 3:
printf("正在退出遊戲...");
Sleep(500); //產生延遲效果,使玩家看清楚打印的內容
system("cls");
exit(0);
default:
gotoxy(i_min + 15, j_max + 2);
printf("請輸入1~3之間的數!!!");
Sleep(300);
system("cls");
start_play();
}
}
int main(void)
{
start_play();
return 0;
}
首先爲什麼頭文件是 C,但卻說代碼是 C++ 而不是 C 呢?
其實很簡單,constexpr
關鍵字是屬於C++ 的,作用相比 const
加快了代碼編譯速度,除此之外可以說剩下內容都是屬於 C,運行用的是 VS 2019 C++ 編譯器運行
Windows API 是 Windows 應用程序接口,內包括幾千個可調用的函數,下面就來講解代碼運用的幾個函數與結構:
- COORD
COORD 是 Windows API 中定義的一種結構體,表示一個字符在控制檯屏幕上的座標,它的原型如下:
typedef struct _COORD {
SHORT X;
SHORT Y;
};
//其中 SHORT 也是個別名
typedef short SHORT;
-
HANDLE 與 handle
HANDLE 是一個通用句柄,是windows用來表示對象的
handle 是一個標誌符(用戶編程時起的名字,或者稱變量) -
STD_INPUT_HANDLE
STD_INPUT_HANDLE 是標準輸入的句柄 -
GetStdHandle
GetStdHandle 是一個 Windows API 函數,它用於從( 標準輸入 或 標準輸出 )中取得一個句柄,它的原型如下:
GetStdHandle(
_In_ DWORD x
);
//其中 DWORD 也是個別名
typedef unsigned long DWORD;
綜上所述:
源代碼中的
const HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
也可以理解爲
const int a = getchar();
- SetConsoleTextAttribute
SetConsoleTextAttribute 是一個可以在 API 中設置控制檯窗口字體顏色的函數,它的原型如下:
SetConsoleTextAttribute(
_In_ HANDLE x, //第一個參數爲 HANDLE
_In_ WORD y //第二個參數爲 WORD 類型
);
//其中 WORD 也是個別名
typedef unsigned short WORD;
- SetConsoleCursorPosition
SetConsoleCursorPosition 是一個可以設置控制檯(cmd)光標位置的函數,它的原型如下:
SetConsoleCursorPosition(
_In_ HANDLE x, //第一個參數爲 handle
_In_ COORD y //第二個參數爲 COORD 類型
);
讀取文件 與 寫入文件
File 屬於 Web API 接口,以源代碼中的file_scanf()
和 file_printf()
函數爲例
void file_scanf()
{
FILE* fp; //建立一個文件操作指針
fopen_s(&fp, "./save.txt", "r+"); //將指向這個文件的文件流給 fp ,"r+" 表示將打開並讀取和寫入
//僅當中間的文件存在,上述操作才能成功
if (fp != NULL)
fscanf_s(fp, "%d", &max); //從 fp 文件中讀取 int型整數 輸入到 max 中
fclose(fp); //關閉文件
}
void file_printf()
{
FILE* fp; //建立一個文件操作指針
fopen_s(&fp, "./save.txt", "w+"); //將指向這個文件的文件流給 fp ,"w+" 表示打開一個文件進行讀取
//若該文件不爲空則其原內容將被銷燬,變成空白
if (fp != NULL)
fprintf(fp, "%d", i); //將 i 以 int型整數的格式輸入到 fp 文件中
fclose(fp); //關閉文件
}
貪喫蛇是如何移動的?
首先我們看看蛇的結構體
struct snake {
int x;
int y;
struct snake* next; //下一個部分
};
//這明顯是一個鏈表,記錄蛇的身體的每個座標
用於移動身體的 change_body()
和 MovingBody()
函數
void MovingBody()
{
int a = head->x, b = head->y; //先記錄原頭部座標
snake* p = head;
while (p->next != NULL)
{
gotodelete(p->x, p->y); //通過先清空後打印實現動畫效果
p = p->next;
}
switch (click) //對按下的鍵盤進行判斷
{
case up:
head->y -= 1;
change_body(a, b);
break;
case down:
head->y += 1;
change_body(a, b);
break;
case left:
head->x -= 2; //因爲顯示器長寬是 1:2
change_body(a, b);
break;
case right:
head->x += 2;
change_body(a, b);
break;
case stop:
break;
case add_speed:
if (sleeptime >= 50)
{
sleeptime -= 10;
i += 5;
}
refresh();
break;
case down_speed:
if (sleeptime <= 200)
{
sleeptime += 10;
i -= 5;
}
refresh();
break;
}
p = head;
eating(); //看蛇是否喫到東西(是的話鏈表會多一個結構體,用於表示尾巴)
while (p->next != NULL) //打印蛇
{
gotoprint(p->x, p->y);
p = p->next;
}
p = head;
gotoxy(0, 28);
Sleep(sleeptime); //刷新頻率(貪喫蛇移動速度)
}
void change_body(int a, int b)
{
snake* p = head; //當前頭部
int x, y, x1, y1;
x = a;
y = b;
p = p->next;
while (p->next != NULL) //往前替換
{
x1 = p->x;
y1 = p->y;
p->x = x;
p->y = y;
x = x1;
y = y1;
p = p->next;
}
}
總體評價源代碼:
優點: 各個函數,條理清晰,分工明確,能使人一目瞭然;恰當的使用了 Windows API,每個內嵌函數都有着不可缺少作用;玩法好評,除了開頭的數字跳轉游戲和說明,可加速 和 減速也是一大亮點
bug: 貪喫蛇在當前方向上按下反方向鍵會自己喫掉自己
建議: 對鍵位生效進行判斷,若移動方向與鍵位方向相反,則鍵位效果失效
bug2: 在開頭輸入非數字,程序會直接卡掉
建議: 這個其實是 switch 語句的鍋,該 switch 語句只能識別數字,解決方法暫時沒想出來