C語言版貪喫蛇:第三部分

C語言版貪喫蛇:第三部分

概述

經過前兩章的學習,我們已經基本實現了遊戲的一半功能

  1. 繪製圍牆
  2. 繪製蛇,繪製食物

本章將要學習

如何使我們的蛇動起來

這是貪喫蛇遊戲中最重要的功能,實現了運動,遊戲的主體也就完成了。

蛇的運動

設計思路

  • 設置一個while 循環語句,判斷條件是是否有鍵盤讀入
  • 如果有鍵盤讀入,根據讀入的按鍵,選擇是否改變運動方向。
  • 如果沒有鍵盤讀入,則保持運動狀態


重繪的方法

  • 設置一個clear()函數,在原來蛇的地方打印空格,相當於把原來的蛇“擦掉”
  • 根據鍵盤讀入的按鍵,改變每一節蛇的座標
  • 再用 上一章的 printsnake( ) 把新的蛇打印出來

鍵盤讀入函數

  • 需要頭文件 conio.h
  • kbhit( ) :判斷是否有按鍵輸入
  • getch( ) :讀取按鍵,但不會在屏幕上顯示出來


具體實現

定義變量

  1. direation: 存儲當前運動方向(即鍵盤輸入)
  2. direation_pre: 讀入按鍵後,存儲上一次的運動方向
  3. 主要是要避免原來向上運動,而鍵盤讀入是下,產生bug。
  4. 字符型是爲了存儲 W,A,S,D


全局變量,參考代碼

// 定義三個變量存 蛇長,蛇頭,蛇尾
int lenth, head, nail;
// 食物位置
int food_x, food_y;
// 儲存蛇的當前,和上一次的移動方向
char direation,direation_pre;


clear()函數

  1. 代碼其實和printsnake( )基本一樣,可以合併
  2. 這裏爲了理解方便重設了一個
  3. 將原來在座標位置上答應 * (星號) 改爲空格即可


參考代碼如下:

// 清除蛇
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( )函數測試

  1. 現在這個部分只是做測試用的
  2. 以clear( ) 裏面的for循環爲模板
  3. 將打印部分替換成改變座標


參考代碼如下:
實現蛇的座標右移一格

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( ) 函數

回顧一下上章中介紹的運動原理

考慮到,每運動一次,實際上只有蛇頭和蛇尾的位置有變化。可以通過將蛇尾接到蛇頭前面,就可以得到新的蛇位置。

我們將基於這個原理進行程序的編寫

  1. 根據 direation 變量的值選擇運動方向
  2. 鍵盤讀入的值直接存到 direation 裏
  3. direation的值改變則運動方向改變
  4. 不斷的將蛇尾接到蛇頭前實現運動

參考代碼:
只有 ‘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即可改變蛇的運動方向
記得切換成英文輸入法

這裏寫圖片描述



總結

通過本章的學習,我們已經實現了貪喫蛇的核心部分

運動

在下一章中,我們將要實現最後的幾個功能

  1. 喫食物增長身體
  2. 判斷是否撞牆,喫到自己



本章完整代碼如下

/*
    這裏是貪喫蛇源代碼

    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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章