前言
貪喫蛇相信大家小時候都玩過,那麼你有沒有想過有一天你也能自己編程實現它呢?今天我就在這裏教大家用C語言,通過自頂向下逐步求精的方法,實現一個簡易的貪喫蛇。
準備工作
(首先心裏要有點X數)既然是簡易的,那麼沒有高大上的畫面,我們實現的一定是字符版。爲了方便我們之後的思考,我們先把最基礎的幾個參數和初始參數定下來。
#include<stdio.h>
#include<conio.h>
#define SNAKE_MAX_LENGTH 20
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define WALL_CELL '*'
#define SNAKE_FOOD '$'
#define stay 'S'
char map[12][12] =
{ "************",
"*XXXXH *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"************"
};
int snakeX[SNAKE_MAX_LENGTH] = { 5,4,3,2,1 };
int snakeY[SNAKE_MAX_LENGTH] = { 1,1,1,1,1 };
int snake_length = 5;
對沒錯,那個二元字符組就是遊戲界面,第二行那一條就是我們的蛇了。
一條會動、會死的蛇
秉着由簡入繁的原則,我們先來實現最基本的功能,就是一條能夠接受我們命令(WASD)動起來,而且撞到牆或者自己會死的蛇。讓我們先敲下僞代碼。
輸出字符矩陣
WHILE not 遊戲結束 DO
ch=輸入的字符
move(ch);
輸出字符矩陣
END WHILE
輸出 Game Over!!!
然後按照僞代碼敲下main函數,以及我們秉着“自頂向下、逐步求精”的原則自己寫的move函數以及判斷死亡的函數。這裏用到了兩個小技巧,一個是getch()這個函數,可以讓程序不需要每次輸入後再敲個回車;另一個是p這個值(其實就是布爾值)來幫助記錄蛇的死活。
void print(char map[12][12]) {
int i;
for (i = 0; i < 12; i++) {
int j;
for (j = 0; j < 12; j++) {
printf("%c", map[i][j]);
}
printf("\n");
}
}
int hit_it_self(int X[], int Y[]) {
int i;
for(i = 2; i < snake_length; i++)
if (X[0] == X[i] && Y[0] == Y[i]) return 0;
return 1;
}
int hit_the_wall(int X[], int Y[]) {
if (X[0] == 0 || X[0] == 11) return 0;
if (Y[0] == 0 || Y[0] == 11) return 0;
return 1;
}
void move(int x) {
map[snakeY[snake_length - 1]][snakeX[snake_length - 1]] = ' ';
map[snakeY[0]][snakeX[0]] = 'X';
int i;
tempX = snakeX[snake_length - 1];
tempY = snakeY[snake_length - 1];
for (i = 1; i < snake_length; i++) {
snakeX[snake_length - i] = snakeX[snake_length - i - 1];
snakeY[snake_length - i] = snakeY[snake_length - i - 1];
}
switch (x) {
case 'A':
snakeX[0] = snakeX[0] - 1;
break;
case 'W':
snakeY[0] = snakeY[0] - 1;
break;
case 'D':
snakeX[0] = snakeX[0] + 1;
break;
case 'S':
snakeY[0] = snakeY[0] + 1;
break;
}
map[snakeY[0]][snakeX[0]] = 'H';
}
int main() {
print(map);
int p = 1;
char movement;
while (p>0) {
movement = getch();
int x = movement;
move(x);
int p1 = hit_it_self(snakeX, snakeY);
int p2 = hit_the_wall(snakeX, snakeY);
if (p1 == 0 || p2 == 0) p = 0;
else {
system("cls");//清屏
print(map);
}
}
printf("GAME OVER");
}
放食物,喫食物
貪喫蛇的另一功能就是喫食物,然後變長。這裏有兩個難點:一個就是放置食物需要隨機,而C語言的隨機是隨機數表法生成的僞隨機,所以我們這裏就需要一點小技巧,根據啓動程序的時間來決定隨機的seed;另一個則是喫食物後我要實現立刻再生成一個食物,以及蛇的身體長度加一。下面就是相關的函數。
int food = 0;
int foodX, foodY;
int tempX, tempY;
void set_food(char map[12][12]){
srand((int)time(0));
foodX = (int)(rand()%10) + 1;
foodY = (int)(rand()%10) + 1;
int judge = 1;
int i;
for(i =0; i < snake_length; i++){
if(snakeX[i] == foodX && snakeY[i] == foodY){
judge = 0;
break;
}
}
if(judge == 1){
map[foodY][foodX] = '$';
food = 1;
//return;
}
//if(judge == 0) set_food(map);如果迭代就會偶爾導致程序崩潰。
}
void eat(char map[12][12]){
snake_length++;
snakeX[snake_length - 1] = tempX;
snakeY[snake_length - 1] = tempY;
map[tempY][tempX] = 'X';
food = 0;
}
自己會動
接下來就是最後的部分,我需要蛇能夠自己動起來。就是說,當我沒有輸入的時候,蛇要自己沿着原方向繼續走。這裏我們用了兩個函數,一個是kbhit()來解決輸入的問題,另一個則是clock()或者sleep()來解決自己動的問題,我這裏選擇的是前者。其中kbhit的功能是有輸入則返回輸入值,沒有則返回0;clock()則是返回程序執行的時間。
具體代碼如下:(這裏筆者就將整個main函數再重新敲一遍了。)
char my_kbhit(char movement){
if(kbhit()){
movement = getch();
return movement;
}
else return movement;
}
int main() {
int game_time;
print(map);
int p = 1;
char movement = stay;
while (p>0) {
game_time = clock();
if(game_time % (1000 - 50 * snake_length) == 0) {
movement = my_kbhit(movement);
while(food == 0){
if(snake_length <= 20){
set_food(map);
}
}
int x = movement;
move(x);
int p1 = hit_it_self(snakeX, snakeY);
int p2 = hit_the_wall(snakeX, snakeY);
if (p1 == 0 || p2 == 0) p = 0;
else {
if(snakeX[0] == foodX && snakeY[0] == foodY) eat(map);
system("cls");
print(map);
}
}}
if(snake_length == 20){
system("cls");
printf("YOU WIN");
return;
}
printf("GAME OVER");
}
這裏爲了增加難度,我讓被模的數隨蛇的長度增加而減少,從而實現了蛇越長動得越快來給遊戲增加樂趣。
後記
學會了貪喫蛇,其實很多小遊戲的字符版就都可以實現了,比如 flappy bird、 或者一個簡單的“雷電”。當然讀者也可以自如發揮來美化這個程序,比如利用 system 的種種函數來設計邊框,背景顏色,亦或者想個辦法讓打印的時候不那麼閃,這些就留給讀者進一步自由發揮了。
總之目前的效果就是這樣啦。