C++ 實現貪喫蛇

本博客的主要目的是學習 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 語句只能識別數字,解決方法暫時沒想出來

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