一、思考貪吃蛇的思路:
1.在一個窗口繪製貪吃蛇,肯定少不了繪製的API,所以必不可少需要引入graphics.h這個圖形界面庫(裏面封裝了WIN 32大部分繪製API,如果沒有的話,可以用GDI)。
2.貪吃蛇吃掉食物就會增長一點,可以把它看成是一節一節的。
3.蛇要移動,肯定會有座標的變化。
4.既然蛇會移動,那麼蛇吃掉食物的時候食物當然也是隨機變化的,所以食物也有座標。
5.蛇的碰撞檢測(蛇頭碰到邊界或者碰到自己的身體某部分,就死亡(game over))。
6.蛇要有方向纔可以
7.需要讀取鍵盤操作,改變蛇的方向
二、補充graphics.h圖形庫的基礎知識
1.創建一個繪製窗口
API:
void initgraph(int width,int height) //創建寬x,高y的圖形窗口
2.清除窗口屏幕所有繪製
API:
void cleardevice() //清空整個窗口的繪製
3.在指定位置輸出文字
API:
void settextxy(int x,int y, LPCTSTR str) //在座標爲(x,y)處輸出文字str
void outtextxy( int x, int y, TCHAR c ) // 兩種字符串類型
4.繪製矩形
API:
fillrectangle(int left, int top, int right, int bottom) //畫填充矩形,從起點(left,top) 到終點(right,bottom)
5.繪製圓角矩形
API:
fillroundrect(int left, int top, int right, int bottom, int ellipsewidth, int ellipseheight) //畫填充矩形,從起點(left,top) 到終點(right,bottom) ellipsewidth構成圓角矩形的圓角的橢圓的寬度。ellipseheight構成圓角矩形的圓角的橢圓的高度。若後兩個相當相當於繪製一個圓形 不過不是以圓心的 是按照矩形的。
6.設置當前文字填充色
API:
settextcolor(COLOR color) //設置當前文字也就是下一行文字的顏色 宏定義RED,BLUE,YELLOW等
7.設置當前文字風格
API:
settextstyle(int nHeight, int nWidth, LPCTSTR lpszFace, int nEscapement, int nOrientation, int nWeight, bool bItalic, bool bUnderline, bool bStrikeOut) //設置文字寬高 如果 width = 0就是自適應大小,LPCTSTR輸出的文字後面可以省略掉
三、如何創建蛇和食物
蛇和食物有一個共同的特點就是都有座標
所以
//創建座標
struct Coor { // x y 座標結構體
int x;
int y;
};
其次創建蛇和食物結構(其實可以見蛇和食物的對象都可以的)
//蛇
struct Snake { //蛇結構
int n; //當前的節數
Coor szb[SNAKELENGTH]; //蛇座標 最大長度是500
Ch ch; //蛇的方向
}snake; //結構體變量
//食物
struct Food
{
Coor fzb; //食物的座標 只有一個
int flag; //是否被吃的標誌
}food;
當然,少不了方向 方向無非就是上下左右 可以使會用枚舉類型定義
enum Ch
{
up = 72,
down = 80,
left = 75,
right = 77
};
四、編寫思路(過程)
定義完這些結構體後 我們知道 這個蛇也就是snake.szb[0].x 和snake.szb[0].y就是蛇頭
所以只需要移動蛇頭就可以。
爲什麼?because 首先蛇是一節一節的也就是繪製的一個一個小矩形,並且這個矩形是根據上面定義的snake結構的座標結構數組存放的x,y座標來繪製的,當蛇移動的時候 只需要將座標加一個步長就可以了。
這樣就真的對了嗎?每次移動就在那個放下加或者減?
其實不對,爲什麼?because 如果是蛇變了很多次方向就像這樣
應該使用怎麼樣的思路來進行所有蛇節方向改變呢?
可以讓蛇的最後一節n的x,y座標每次繪製的時候等於蛇的n-1的座標 這樣不就可以改變方向並且按蛇頭走的路原封不動的走一遍嗎? 對吧
所以 移動蛇的時候應該有一個循環,根據蛇節數來遍歷的
for (int i = snake.n - 1; i > 0; i--)
{
snake.szb[i].x = snake.szb[i - 1].x;
snake.szb[i].y = snake.szb[i - 1].y;
}
創建蛇(這裏一共創建了4個蛇節 初始化的時候就是四個蛇節):
snake.szb[3].x = 50; //當前節數的X座標
snake.szb[3].y = 0; //當前節數的Y座標
snake.szb[2].x = 40;
snake.szb[2].y = 0;
snake.szb[1].x = 30;
snake.szb[1].y = 0;
snake.szb[0].x = 20;
snake.szb[0].y = 0;
那麼蛇的如何判斷方向呢(你需要引入頭文件 <conio.h>)?
//*****這是判斷按鍵 來改變方向
int move = _getch();
switch (move)
{
case up: //不能向下走
if(snake.ch!=down)
snake.ch = up;
break;
case down:
if (snake.ch != up)
snake.ch = down;
break;
case right:
if (snake.ch != left)
snake.ch = right;
break;
case left:
if (snake.ch != right)
snake.ch = left;
break;
}
//***********/
//********根據方向變化來移動蛇頭
//先將後一節的座標等於前一節的座標
for (int i = snake.n - 1; i > 0; i--)
{
snake.szb[i].x = snake.szb[i - 1].x;
snake.szb[i].y = snake.szb[i - 1].y;
}
switch (snake.ch)
{
case up:
snake.szb[0].y -= NUM;
break;
case down:
snake.szb[0].y += NUM;
break;
case right:
snake.szb[0].x += NUM;
break;
case left:
snake.szb[0].x -= NUM;
break;
}
//********/
這裏的_getch 用於獲取按鍵ASCII 比如 _getch 獲取鍵盤按鍵 _kbhit() 檢測是否按下某個鍵無則返回0;
接下來就是每次移動繪製蛇:
//繪製蛇 (根據存儲的蛇節n)來
for (int i =0; i < snake.n ; i++)
{
if (i == 0)
{
setfillcolor(YELLOW); //填充顏色
}
else {
setfillcolor(RED); //填充顏色
}
fillrectangle(snake.szb[i].x, snake.szb[i].y, snake.szb[i].x + size, snake.szb[i].y + size);
}
接下來應該是食物了
食物是隨機變化的 並且必須是蛇頭吃掉食物才能再次隨機生成一個食物。
所以開始的時候應該封裝一個函數進行初始化食物
food.fzb.x = (rand() % WINDOW_WIDTH / 10) * 10; // 0 1 2 3 4 .. 639
food.fzb.y = (rand() % WINDOW_HEIGHT / 10) * 10; // 0 1 2 3 ... 479;
//蛇的座標是10 20 30 40 有規律的 10的倍數
food.flag = 1; //沒有吃掉 1 隨機出現
爲什麼要 *10?因爲設置的窗口是640 * 480 食物的座標必須和蛇節大到小和每次的步長一致,這樣當蛇頭與食物重合就可以判斷吃掉了食物
//繪製食物
void DrawFood()
{
setfillcolor(GREEN);
//繪製圓形食物
fillroundrect(food.fzb.x, food.fzb.y, food.fzb.x + 10, food.fzb.y + 10,10,10);
}
有了上面說的碰撞的思路
部分代碼:
if (snake.szb[0].x < 0 || snake.szb[0].x >= WINDOW_WIDTH || snake.szb[0].y >= WINDOW_HEIGHT || snake.szb[0].y < 0)
{
return true;
}
。下面是main
//初始化隨機種子
srand((unsigned int)time(NULL));
//初始化後會有一個像素矩陣,可以使用座標系去表示它
InitSnake();
/*
改變方向.按鍵之後
按鍵之前程序在繼續移動和繪圖
*/
char buffer[100];
while (1)
{
while (!_kbhit()) //檢測是否按下某個鍵 沒有按下就返回0
{
if (food.flag==0)
{
CoorFood();
}
cleardevice(); //清除重繪
if (isCrash()) //如果碰撞了
{
NUM = 0; //表示撞牆 每次移動的步伐10 變成0 不走了
}
if (NUM != 0) //
{
rectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
settextstyle(30, 0, TEXT("微軟雅黑"));
outtextxy(WINDOW_WIDTH + ((WINDOW_WIDTH + 200) - WINDOW_WIDTH) / 2 - 30, 50, TEXT("貪吃蛇"));
settextstyle(25, 0, TEXT("微軟雅黑"));
settextcolor(YELLOW);
outtextxy(WINDOW_WIDTH + ((WINDOW_WIDTH + 200) - WINDOW_WIDTH) / 2 - 60, 80, TEXT("分數:"));
settextstyle(60, 0, TEXT("微軟雅黑"));
settextcolor(WHITE);
_itoa_s(FenShu, buffer, _countof(buffer), 10);
outtextxy(WINDOW_WIDTH + ((WINDOW_WIDTH + 200) - WINDOW_WIDTH) / 2 - 60, 120, LPCTSTR(buffer));
MoveSnake();
DrawFood();
DrawSnake(10);
EatFood();
Sleep(100);
}
else {
break;
}
}
if (NUM == 0)
{
break;
}
ChangeSnakeCh(); //按下後執行方向改變函數
}
settextstyle(65, 0, TEXT("仿宋"));
outtextxy((WINDOW_WIDTH + 200) / 2 - 120, WINDOW_HEIGHT / 2 - 50, TEXT("你掛了!"));
settextstyle(33, 0, TEXT("仿宋"));
settextcolor(YELLOW);
outtextxy((WINDOW_WIDTH + 200) / 2 - 120, WINDOW_HEIGHT / 2 + 20, TEXT("分數:"));
outtextxy((WINDOW_WIDTH + 200) / 2 , WINDOW_HEIGHT / 2 + 20, LPCTSTR(buffer));
我的貪吃蛇的源代碼已經在github上面
github地址:https://github.com/IAmWilling/SnakeGame
源碼裏面有詳細的註釋,可以對照代碼根據這篇總結,來讀。