C語言版貪喫蛇:第三部分
概述
經過前兩章的學習,我們已經基本實現了遊戲的一半功能
- 繪製圍牆
- 繪製蛇,繪製食物
本章將要學習
如何使我們的蛇動起來
這是貪喫蛇遊戲中最重要的功能,實現了運動,遊戲的主體也就完成了。
蛇的運動
設計思路
- 設置一個while 循環語句,判斷條件是是否有鍵盤讀入
- 如果有鍵盤讀入,根據讀入的按鍵,選擇是否改變運動方向。
- 如果沒有鍵盤讀入,則保持運動狀態
重繪的方法
- 設置一個clear()函數,在原來蛇的地方打印空格,相當於把原來的蛇“擦掉”
- 根據鍵盤讀入的按鍵,改變每一節蛇的座標
- 再用 上一章的 printsnake( ) 把新的蛇打印出來
鍵盤讀入函數
- 需要頭文件 conio.h
- kbhit( ) :判斷是否有按鍵輸入
- getch( ) :讀取按鍵,但不會在屏幕上顯示出來
具體實現
定義變量
- direation: 存儲當前運動方向(即鍵盤輸入)
- direation_pre: 讀入按鍵後,存儲上一次的運動方向
- 主要是要避免原來向上運動,而鍵盤讀入是下,產生bug。
- 字符型是爲了存儲 W,A,S,D
全局變量,參考代碼
// 定義三個變量存 蛇長,蛇頭,蛇尾
int lenth, head, nail;
// 食物位置
int food_x, food_y;
// 儲存蛇的當前,和上一次的移動方向
char direation,direation_pre;
clear()函數
- 代碼其實和printsnake( )基本一樣,可以合併
- 這裏爲了理解方便重設了一個
- 將原來在座標位置上答應 * (星號) 改爲空格即可
參考代碼如下:
// 清除蛇
void clear()
{
int i, j = head;
for (i = 1; i <= lenth; i++)
{
// 注意 裏面的參數是j,j從蛇頭移動到蛇尾
gotoxy(Snake[j].x, Snake[j].y);
printf(" ");
j = Snake[j].next;
}
}
move( )函數測試
- 現在這個部分只是做測試用的
- 以clear( ) 裏面的for循環爲模板
- 將打印部分替換成改變座標
參考代碼如下:
實現蛇的座標右移一格
void move()
{
int i, j = head;
for (i = 1; i <= lenth; i++)
{
Snake[j].x++;
j = Snake[j].next;
}
}
測試時的主函數:
- 在主函數裏設置一個while循環
- 依次調用三個函數
- clear( )
- move( )
- printsnake( )
- 實現蛇的不斷右移
- 測試後會發現蛇移動太快了
- 這裏加入一個Sleep函數,讓程序中途停止一會
- Sleep( 參數); 單位是毫秒
參考代碼如下
int main()
{
init(); // 初始化函數,繪製圍牆
while(1)
{
clear();
move();
printsnake();
Sleep(150);
}
system("pause");
return 0;
}
正式版 move( ) 函數
回顧一下上章中介紹的運動原理
考慮到,每運動一次,實際上只有蛇頭和蛇尾的位置有變化。可以通過將蛇尾接到蛇頭前面,就可以得到新的蛇位置。
我們將基於這個原理進行程序的編寫
- 根據 direation 變量的值選擇運動方向
- 鍵盤讀入的值直接存到 direation 裏
- direation的值改變則運動方向改變
- 不斷的將蛇尾接到蛇頭前實現運動
參考代碼:
只有 ‘w’ 一個case,剩下三個自行補齊,或者到文末的完整代碼處查看
//原來move()裏的測試代碼刪掉
// 實現蛇的座標改變
void move()
{
// 擦掉原來的蛇
clear();
// 根據 direation 選擇運動方向
switch (direation)
{
case 'w':
{
// 蛇尾變到蛇頭前面,向上運動,則縱座標減一(回憶之前介紹的座標軸)
Snake[nail].x = Snake[head].x;
Snake[nail].y = Snake[head].y - 1;
// 將新蛇頭的下一個位置指向舊蛇頭
Snake[nail].next = head;
// 將舊蛇頭的前一個位置指向新蛇頭
Snake[head].pre = nail;
// 新蛇頭是原來的蛇尾
head = nail;
// 新蛇尾是原來蛇尾的前一節
nail = Snake[nail].pre;
break;
//注意下面還有一個printsnake()
}
case 'a':
{
...
}
case 's':
{
...
}
case 'd':
{
...
}
}
printsnake();
}
還沒完!
記得給direation,direation_pre賦初始值
可以在init( )初始化函數中進行賦值。
void init()
{
...
direation = 's';
direation_pre='s';
...
}
主函數的編寫
1.前面介紹的 kbhit( ) 函數將在這裏用到,如果沒有按鍵讀入則返回0
2.getch( )從鍵盤讀入一個字符,不顯示在屏幕上
主函數的參考代碼如下:
int main()
{
init(); // 初始化函數,繪製圍牆
while (1)
{
// 沒有讀入則按照之前的方向一直運動
while (!kbhit())
{
move();
Sleep(150);
}
// 有讀入則停下來改變方向
// 先記下原來的方向
direation_pre = direation;
// 讀入要改變的方向
direation = getch();
// 判斷方向是否合法,如果和上次相反,則保持原來方向
if (direation == 'w' && direation_pre == 's' || direation == 's' && direation_pre == 'w')
direation = direation_pre;
if (direation == 'a' && direation_pre == 'd' || direation == 'd' && direation_pre == 'a')
direation = direation_pre;
}
system("pause");
return 0;
}
效果如下,通過小寫的wasd即可改變蛇的運動方向
記得切換成英文輸入法
總結
通過本章的學習,我們已經實現了貪喫蛇的核心部分
運動
在下一章中,我們將要實現最後的幾個功能
- 喫食物增長身體
- 判斷是否撞牆,喫到自己
本章完整代碼如下
/*
這裏是貪喫蛇源代碼
chapter 1:
解釋下頭文件:
time.h 生成隨機數要用到
windows.h 要用到裏面的函數 gotoxy
---------
1.畫圍牆
===========================
chapter 2:
目標:繪製蛇,食物
蛇的存儲結構:簡單的鏈表(數組實現功能)
食物:隨機數的生成
===========================
chapter 3:
目標: 實現蛇的運動
判斷鍵盤輸入
改變蛇的座標
重繪圖像
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>
#include <conio.h>
// 定義一個結構體用來存儲蛇的每節身體的座標
struct snake
{
int x, y;
int next; //保存當前節點的下一個節點的位置(數組下標)
int pre; //保存上一個節點的位置
} Snake[100];
// 定義三個變量存 蛇長,蛇頭,蛇尾
int lenth, head, nail;
// 食物位置
int food_x, food_y;
// 儲存蛇的當前,和上一次的移動方向
int direation, direation_pre;
// 光標移動函數
void gotoxy(int x, int y)
{
COORD coord = {x, y};
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
// 打印蛇
void printsnake()
{
int i, j = head;
for (i = 1; i <= lenth; i++)
{
// 注意 裏面的參數是j,j從蛇頭移動到蛇尾
gotoxy(Snake[j].x, Snake[j].y);
printf("*");
j = Snake[j].next;
}
}
// 清除蛇
void clear()
{
int i, j = head;
for (i = 1; i <= lenth; i++)
{
// 注意 裏面的參數是j,j從蛇頭移動到蛇尾
gotoxy(Snake[j].x, Snake[j].y);
printf(" ");
j = Snake[j].next;
}
}
// 實現蛇的座標改變
void move()
{
// 擦掉原來的蛇
clear();
// 根據 direation 選擇運動方向
switch (direation)
{
case 'w':
{
// 蛇尾變到蛇頭前面,向上運動,則縱座標減一(回憶之前介紹的座標軸)
Snake[nail].x = Snake[head].x;
Snake[nail].y = Snake[head].y - 1;
// 將新蛇頭的下一個位置指向舊蛇頭
Snake[nail].next = head;
// 將舊蛇頭的前一個位置指向新蛇頭
Snake[head].pre = nail;
// 新蛇頭是原來的蛇尾
head = nail;
// 新蛇尾是原來蛇尾的前一節
nail = Snake[nail].pre;
break;
}
case 's':
{
Snake[nail].x = Snake[head].x;
Snake[nail].y = Snake[head].y + 1;
Snake[nail].next = head;
Snake[head].pre = nail;
head = nail;
nail = Snake[nail].pre;
break;
}
case 'a':
{
Snake[nail].x = Snake[head].x - 1;
Snake[nail].y = Snake[head].y;
Snake[nail].next = head;
Snake[head].pre = nail;
head = nail;
nail = Snake[nail].pre;
break;
}
case 'd':
{
Snake[nail].x = Snake[head].x + 1;
Snake[nail].y = Snake[head].y;
Snake[nail].next = head;
Snake[head].pre = nail;
head = nail;
nail = Snake[nail].pre;
break;
}
}
printsnake();
}
// 判斷食物是否合法
int ok_food()
{
// 判斷是否和牆重合
// 橫座標不能等於1,或70 ; 縱座標不能等於1,或20
if (food_x == 1 || food_x == 70)
return 0;
if (food_y == 1 || food_y == 20)
return 0;
// 判斷是否和蛇重合
int j = head;
for (int i = 1; i <= lenth; i++)
{
if (food_x == Snake[j].x && food_y == Snake[j].y)
return 0;
j = Snake[j].next;
}
// 都沒有,則返回1
return 1;
}
// 打印食物
void printfood()
{
// 不斷產生隨機數,知道座標符合要求
do
{
srand((unsigned long)time(0));
food_x = rand() % 70;
food_y = rand() % 20;
} while (!ok_food());
// ok_food()爲判斷食物是否合法的函數,合法返回1,不合法返回0
gotoxy(food_x, food_y);
printf("@");
}
// 繪製圍牆
void printwall()
{
/*
chapter 1 繪製圍牆部分
圍牆大小 70*20, 長70,寬20
*/
// 繪製水平圍牆,
for (int i = 1; i <= 70; i++)
{
gotoxy(i, 1);
printf("#");
gotoxy(i, 20);
printf("#");
}
// 繪製豎直圍牆
for (int i = 1; i <= 20; i++)
{
gotoxy(1, i);
printf("#");
gotoxy(70, i);
printf("#");
}
}
void init()
{
printwall();
// 初始化蛇
Snake[1].x = 4;
Snake[1].y = 4;
Snake[2].x = 4;
Snake[2].y = 5;
head = 1;
nail = 2;
lenth = 2;
Snake[head].next = nail;
Snake[nail].pre = head;
direation = 's';
direation_pre = 's';
// 第一次打印蛇
printsnake();
// 第一次打印食物
printfood();
}
int main()
{
init(); // 初始化函數,繪製圍牆
while (1)
{
// 沒有讀入則按照之前的方向一直運動
while (!kbhit())
{
move();
Sleep(150);
}
// 有讀入則停下來改變方向
// 先記下原來的方向
direation_pre = direation;
// 讀入要改變的方向
direation = getch();
// 判斷方向是否合法,如果和上次相反,則保持原來方向
if (direation == 'w' && direation_pre == 's' || direation == 's' && direation_pre == 'w')
direation = direation_pre;
if (direation == 'a' && direation_pre == 'd' || direation == 'd' && direation_pre == 'a')
direation = direation_pre;
}
system("pause");
return 0;
}