程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> [C入門,c入門

[C入門,c入門

編輯:關於C語言

[C入門,c入門


  因為已經寫了食物的實現,所以我不知道到底是該先寫世界的實現還是蛇的實現。因為世界就是一個窗口,可以立刻在世界中看到食物的樣子,對於大多數人來說,如果寫完代碼立刻就能看到效果,那就再好不過了。可是,我最後還是選擇了先寫蛇的實現這篇筆記。如果先寫世界的實現,我就無法按照現在的思路完完整整的寫下去,因為沒有蛇,世界部分的代碼就不完整,看完食物的效果後,我還是得寫蛇的實現,然後又得修改世界部分的代碼,來查看蛇的效果。反反復復,實在折騰不起。所以我打算把食物和蛇的實現都寫完,最後統一看運行效果。

 

蛇和食物一樣,得在世界中創建,所以代碼基本差不多。

Snake * SNK_CreateSnake(World *world, int size, int x, int y)
{
    Snake *snake;

    if (world == 0) return 0;

    if ((snake = (Snake *)SDL_malloc(sizeof(Snake))) == 0) return 0;

    INIT_SNAKE(world, size, x, y);
    SNK_GrowSnake(snake);

    return snake;
}

宏INIT_SNAKE用於初始化Snake結構體。

SNK_GrowSnake函數用於將蛇的長度加一,因為蛇創建出來後只有蛇頭,我必須再次給它加個蛇尾。如果只有蛇頭,當然也能運行,這只是模擬,不是真正的生命體。不過這是畸形蛇,不好看。我還是讓它正常一點,符合常規思維。

 

蛇的身體一節一節的,所以可以看到我在頭文件中用了一個單向鏈表表示蛇的身體。所以銷毀蛇時,我要遍歷整個鏈表才行,然後依次釋放每個身體節點。

void SNK_DestroySnake(Snake *snake)
{
    struct Body *body;

    if (snake != 0)
    {
        if ((body = snake->body)) REMOVE_BODY(body);

        SDL_free(snake);
        snake = 0;
    }
}

宏REMOVE_BODY就是用來遍歷鏈表並釋放身體節點的,我把它定義為一個宏,這顯得有點多次一舉。這麼做主要是因為我不得不定義一個APPEND_BODY宏來增加蛇的身體節點,所以為了和增加節點相對應,我定義了移除節點這個宏。

 

移動蛇的位置分為兩部,移動蛇頭和移動蛇的身體。主要是由於定義的時候我沒有把蛇頭當作身體的一部分,因為身體可以增長,而蛇頭不能增長,所以只能這樣了。

void SNK_MoveSnake(Snake *snake)
{
    struct Body *body;

    if (snake != 0)
    {
        MOVE_SNAKE(snake);

        for (body = snake->body; body; body = body->next)
        {
            MOVE_SNAKE(body);
            body->direction = (body->next != 0) ? body->next->direction : snake->direction;
        }
    }
}

snake表示蛇頭,MOVE_SNAKE(snake)表示移動蛇頭的位置,MOVE_SNAKE(body)表示移動身體的位置。

移動身體需要遍歷鏈表,不過這裡設置身體方向不知道是否有人看懂? 當前身體節點的方向等於下一個身體節點的方向,仔細想想,這是什麼意思?

我對蛇的分析是,蛇只會在尾部追加節點,如果snake->body指向第一個節點first, first指向第二個節點second, 那麼我追加第三個節點就要從snake->body開始遍歷兩次,追加第四個節點就要從snake->body開始遍歷三次。所以我改變了這個沒有效率的行為,我讓snake->body始終指向最後一個身體節點,因此當追加新的身體節點時,直接追加即可,而不用遍歷鏈表。

所以這個for循環其實是從蛇尾向蛇頭方向遍歷的,當蛇頭方向改變時,身體跟著蛇頭變化,蛇尾跟著身體變化。這是蛇能隨意轉彎的關鍵所在。

 

接下來就是畫出蛇的樣子了,和畫食物一樣,我用一連串的矩形表示蛇。

void SNK_DrawSnake(Snake *snake)
{
    SDL_Rect rect;
    struct Body *body;

    if (snake != 0)
    {
        rect.x = snake->x;
        rect.y = snake->y;
        rect.w = rect.h = snake->size;

        if (((snake->world != 0) ? (snake->world->render != 0) : 0))
        {
            SDL_SetRenderDrawColor(snake->world->render,
                                   snake->color.r, snake->color.g,
                                   snake->color.b, snake->color.a);
            SDL_RenderDrawRect(snake->world->render, &rect);

            for (body = snake->body; body; body = body->next)
            {
                rect.x = body->x;
                rect.y = body->y;
                SDL_RenderDrawRect(snake->world->render, &rect);
            }
        }
    }
}

 

對於蛇的增長,有兩個意思:沒有尾巴時,增長的是尾巴。有尾巴時,增長的是身體。

void SNK_GrowSnake(Snake *snake)
{
    struct Body *body;

    if (snake != 0)
    {
        if ((body = (struct Body *)SDL_malloc(sizeof(struct Body))) == 0) return;

        if (snake->body == 0)
        {
            APPEND_BODY(snake, body);
        }
        else
        {
            APPEND_BODY(snake->body, body);
        }
    }
}

 

接下來是檢查碰撞的函數,它主要有兩個用途:1. 當參數rect是蛇頭位置時,用來檢測蛇頭是否咬到自己的身體。2. 當參數rect是食物位置時,用來檢測身體是否碰到食物。咬到自己或者碰到食物,返回1, 否則返回0。

int SNK_HasIntersection(Snake *snake, SDL_Rect rect)
{
    SDL_Rect bodyrect;
    struct Body *body;

    if (snake != 0)
    {
        bodyrect.w = bodyrect.h = snake->size;

        for (body = snake->body; body; body = body->next)
        {
            bodyrect.x = body->x;
            bodyrect.y = body->y;

            if (SDL_HasIntersection(&bodyrect, &rect) != 0)
                return 1;
        }
    }

    return 0;
}

 

在頭文件中,我定義了蛇的兩個狀態:已死或者可以移動。這個函數便是用於檢測蛇的狀態的。返回SNAKE_DIED表示蛇死了;返回SNAKE_MOVABLE表示蛇處於正常狀態,可以自由移動;返回0表示蛇碰到世界的邊界,不可以移動。我沒有實現蛇可以從一邊回到另一邊這種功能,也沒有規定蛇碰到牆就死了。一切盡可能保持簡單!

int SNK_GetSnakeStatus(Snake *snake)
{
    SDL_Rect headrect;

    if (((snake != 0) ? (snake->world != 0) : 0))
    {
        headrect.w = (snake->x > 0 && snake->x < snake->world->w);
        headrect.h = (snake->y > 0 && snake->y < snake->world->h);

        if (headrect.w && headrect.h)
        {
            headrect.x = snake->x;
            headrect.y = snake->y;
            headrect.w = headrect.h = snake->size;

            if (SNK_HasIntersection(snake, headrect) != 0)
                return SNAKE_DIED;

            return SNAKE_MOVABLE;
        }
        else
        {
            switch (snake->direction)
            {
            case SNAKE_UP:
                headrect.x = (snake->y > 0);
                break;
            case SNAKE_DOWN:
                headrect.x = ((snake->y + snake->size) < snake->world->h);
                break;
            case SNAKE_LEFT:
                headrect.x = (snake->x > 0);
                break;
            case SNAKE_RIGHT:
                headrect.x = ((snake->x + snake->size) < snake->world->w);
                break;
            }

            return ((headrect.x != 0) ? SNAKE_MOVABLE : 0);
        }
    }

    return 0;
}

這裡switch語句只有當蛇碰到世界的邊界時才會進入,這一段主要是為了實現一個功能:當蛇碰到世界的邊界時,蛇無法再向前移動,但是蛇可以再次轉彎。

 

未完,待續!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved