1. 程式人生 > 實用技巧 >程式設計程式碼:用C語言來實現下雪效果,這個冬天,雪花很美

程式設計程式碼:用C語言來實現下雪效果,這個冬天,雪花很美

前言

1.本文主要圍繞如何在控制檯上下起一場只有自己能看見的雪

2.是個簡易跨平臺的,主要是C語言

3.動畫採用 1s 40幀,雪花具有x軸速度和y軸速度

4.比較簡單,可以給學生作為C語言結課作業吧.


正文

1.1先簡單處理跨平臺

  本文寫作動機,還是感謝一下大學的啟蒙老師,讓我知道了有條路叫做程式設計師,可以作為工作生存下去.那就上程式碼了.

首先程式碼定位是面向簡單跨平臺,至少讓gcc和vs能夠跑起來.

其實跨平臺都是嚼頭,說白了就是一些醜陋的巨集.真希望所有系統合二為一,採用統一的標準api設計,但這是不可能的,就相當於很早之前的電視制式一樣.

那麼我們先看圍繞跨平臺的巨集


#include #include #include #include /*
* 時間 : 2015年12月26日11:43:22 * 描述 : 應該算過節吧,今天,寫了個雪花特效 程式碼, * *//** 清除螢幕的shell 命令/控制檯命令,還有一些依賴平臺的實現 * 如果定義了 __GNUC__ 就假定是 使用gcc 編譯器,為Linux平臺 * 否則 認為是 Window 平臺*/#ifdefined(__GNUC__)//下面是依賴 Linux 實現#include #definesleep_ms(m) \ usleep(m *1000)//向上移動游標函式 Linuxstaticvoid__curup(int height) { inti
= -1; while(++i printf("\033[1A");//先回到上一行 }#else// 建立等待函式 1s 60 幀 相當於 16.7ms => 1幀, 我們取16ms// 咱麼的這螢幕 推薦 1s 25幀吧 40ms// 這裡建立等待函式 以毫秒為單位 , 需要依賴作業系統實現#include #definesleep_ms(m) \ Sleep(m)//向上移動游標staticvoid__curup(int height) { COORD cr = {0,0}; // GetStdHandle(STD_OUTPUT_HANDLE) 獲取螢幕物件, 設定游標 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cr);
}#endif/*__GNUC__ 跨平臺的程式碼都很醜陋 */

首先是sleep_ms這個巨集,傳入一個毫秒數,讓作業系統等待.

對於__curup實現的不好.功能是讓控制檯當前游標移動到上面的height位置,對於window直接移動到第一行(0,0)位置.

上面一共用了 5個頭檔案還是容易的程式碼.string.h主要用的是memset函式,讓一段記憶體初始化,用0填充.

對於time.h主要是為了初始化時間種子,方便每次執行都不一樣.

// 初始化隨機數種子,改變雪花軌跡srand((unsigned)time(NULL));

1.2再說主業務程式碼

這裡程式設計師執行的主業務,先說一說這裡用的資料結構如下

// 定義初始螢幕的寬高畫素巨集#define_INT_WIDTH        (100)#define_INT_HEIGHT        (50)// 螢幕重新整理幀的速率#define_INT_FRATE        (40)// 雪花飄落的速率,相對於 螢幕重新整理幀 的倍數#define_INT_VSNOW        (10)/** 錯誤處理巨集,msg必須是""括起來的字串常量

* __FILE__        : 檔案全路徑

* __func__        : 函式名

* __LINE__        : 行數行

* __VA_ARGS__    : 可變引數巨集,

* ##表示直接連線, 例如 a##b <=> ab*/#definecerr(msg,...) \    fprintf(stderr, "[%s:%s:%d]"msg"\n",__FILE__,__func__,__LINE__,##__VA_ARGS__);/**  螢幕結構體, 具有 寬高

*  frate  : 繪製一幀的週期, 單位是 毫秒

*  width  : 螢幕的寬,基於視窗的左上角(0,0)

*  height : 螢幕的高

*  pix    : 用一維模擬二維 主要結構如下

*            0 0 0 1 0 0 1 0 1 0

*            0 1 0 1 0 1 0 1 2 0

*            . . .

*            => 0表示沒畫素, 1表示1個畫素,2表示2個畫素....*/struct screen {

    intfrate;// 也可以用 unsigned 結構int width;

    int height;

    char*pix;

};

建立了一個繪圖物件structscreen這裡構建這個結構體的時候用了下面一個技巧

//後面是 為 scr->pix 分配的記憶體 width*heightscr =malloc(sizeof(structscreen) +sizeof(char)*width*height);

一次分配兩個記憶體空間.下面是主要實現的api物件

/** 建立一個 螢幕結構指標 返回

*

* int frate    : 繪製一幀的週期

* int width    : 螢幕寬度

* int height    : 螢幕高度

* return        : 指向螢幕結構的指標

* */structscreen* screen_create(intfrate,intwidth,int height);/** 銷燬一個 螢幕結構指標, 併為其置空

* struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的

* */voidscreen_destory(structscreen** pscr);/**

* 螢幕繪製函式,主要生成一個雪花效果

*

* struct screen* : 螢幕資料

* return          : 0表示可以繪製了,1表示圖案不變*/intscreen_draw_snow(structscreen* scr);/**

* 螢幕繪製動畫效果, 繪製雪花動畫

*

* struct screen* : 螢幕結構指標*/voidscreen_flash_snow(structscreen* scr);

建立銷燬,繪製一個雪花介面,繪製雪花動畫效果的api.其實都很相似,用opengl庫,主要讓我們省略了需要單獨和作業系統顯示層打交道工作.

這裡介紹一下,個人一個簡單避免野指標的的方法,具體看下面實現

/** 銷燬一個 螢幕結構指標, 併為其置空

* struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的

* */voidscreen_destory(structscreen** pscr)

{

    if(NULL == pscr || NULL == *pscr)

        return;

    free(*pscr);

    // 避免野指標*pscr = NULL;

}

在執行之後置空,因為C程式設計師對NULL一定要敏感,形成條件反射.和大家開個玩笑 ,

請問 :

C 語言中, NULL , 0,'\0',"0",false有什麼異同 ?

歡迎同行,在招聘的時候問問,應聘初級開發工作者.為什麼C需要扣的那麼細.因為其它語言.你不明白是什麼,

你可以用的很好.但是C你寫的程式碼,如果不知道會有怎樣的結果,那麼線上就一大片伺服器直接崩掉.而且還很難找出

問題所在.因為C很簡單,越簡單就是越複雜.就越需要專業的維護人員.導致它成了'玩具'.

最後看一下主業務

// 主函式,主業務在此執行intmain(intargc,char*argv[])

{

    structscreen* scr = NULL;

    //建立一個螢幕物件scr = screen_create(_INT_FRATE, _INT_WIDTH, _INT_HEIGHT);

    if(NULL == scr)

        exit(EXIT_FAILURE);

    //繪製雪花動畫    screen_flash_snow(scr);

    //銷燬這個螢幕物件screen_destory(&scr);

    return0;

}

還是非常容易看懂的,建立一個螢幕物件,繪製雪花效果.銷燬螢幕物件.

1.3說一寫介面的實現細節

先看幾個簡單的api實現,建立和銷魂程式碼如下,很直白.

/** 建立一個 螢幕結構指標 返回

*

* int frate    : 繪製一幀的週期

* int width    : 螢幕寬度

* int height    : 螢幕高度

* return        : 指向螢幕結構的指標

* */structscreen*    screen_create(intfrate,intwidth,int height)

{

    structscreen *scr = NULL;

    if(frate<0|| width <=0|| height <=0) {

        cerr("[WARNING]check is frate<0 || width<=0 || height<=0 err!");

        return NULL;

    }

    //後面是 為 scr->pix 分配的記憶體 width*heightscr =malloc(sizeof(structscreen) +sizeof(char)*width*height);

    if(NULL == scr) {

        cerr("[FATALG]Out of memory!");

        return NULL;

    }

    scr->frate = frate;

    scr->width = width;

    scr->height = height;

    //減少malloc次數,malloc消耗很大,記憶體洩露呀,記憶體碎片呀scr->pix = ((char*)scr) +sizeof(struct screen);

    return scr;

}/** 銷燬一個 螢幕結構指標, 併為其置空

* struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的

* */voidscreen_destory(structscreen** pscr)

{

    if(NULL == pscr || NULL == *pscr)

        return;

    free(*pscr);

    // 避免野指標*pscr = NULL;

}

後面說一下如何繪製螢幕中雪花

主要演算法是

a.有個螢幕wxh

b.螢幕從上面第一行出雪花 ,出雪花位置是隨機的[0,w],但是有個距離,這個距離內只有一個雪花

c.下一行雪花依賴上一行雪花的生成,每個雪花在可以飄動的時候,只能在[-1,1]範圍內

d.實現動畫效果就是每畫一幀就等待一段時間

下面看具體一點的a

//建立一個螢幕物件scr = screen_create(_INT_FRATE, _INT_WIDTH, _INT_HEIGHT);

scr物件就是我們的建立螢幕. _INT_WIDTH和 _INT_HEIGHT就是螢幕大小.對於_INT_FRATE表示繪製一幀時間.

b實現程式碼如下:

//構建開頭 的雪花,下面巨集表示每 _INT_SHEAD 個步長,一個雪花,需要是2的冪//static 可以理解為 private, 巨集,位操作程式碼多了確實難讀#define_INT_SHEAD (1<<2)staticvoid__snow_head(char* snow,int len)

{

    intr =0;

    //資料需要清空memset(snow,0, len);

    for (;;) {

        //取餘一個技巧 2^3 - 1 = 7 => 111 , 並就是取餘數intt = rand() & (_INT_SHEAD -1);

        if(r + t >= len)

            break;

        snow[r + t] =1;

        r += _INT_SHEAD;

    }

}#undef_INT_SHEAD

技巧如上,可以看說明.這裡科普一下,對於for(;;) {}和while(true) {}異同.

for(;;) {}和while(true) {}這兩段程式碼轉成彙編是一樣的,不一樣的是強加的意願.第一個希望跳過檢測步驟速度更快一點.

再擴充套件一點.

//另一種 迴圈語句, goto 還是 很強大實用的__for_loop:

    if(false)

        goto __for_break;     

goto __for_loop;

__for_break:

可以再擴充套件深一點,還有一種api比這個goto還NB.有機會分享.特別強大,是異常處理程式本質.

對於c.

//通過 上一個 scr->pix[scr->width*(idx-1)] => scr->pix[scr->width*idx]//下面的巨集 規定 雪花左右搖擺 0 向左一個畫素, 1 表示 不變, 2表示向右一個畫素#define_INT_SWING (3)staticvoid__snow_next(structscreen* scr,int idx)

{

    intwidth = scr->width;

    char* psnow = scr->pix + width*(idx -1);

    char* snow = psnow + width;

    inti, j, t;// i索引, j儲存下一個瞬間雪花的位置,t 臨時補得,解決雪花重疊問題

                //為當前行重置memset(snow,0, width);

    //通過上一次雪花位置 計算下一次雪花位置for(i =0; i

        for(t = psnow[i]; t>0; --t) {// 雪花可以重疊

                                      // rand()%_INT_SWING - 1 表示 雪花 橫軸的偏移量,相對上一次位置j = i + rand() % _INT_SWING -1;

            j = j<0? width -1: j >= width ?0: j;// j如果越界了,左邊越界讓它到右邊,右邊越界到左邊++snow[j];

        }

    }

}

下一行雪花依賴上一行雪花,這裡有點像插入排序.

整體的繪製程式碼如下

/**

* 螢幕繪製函式,主要生成一個雪花效果

*

* struct screen* : 螢幕資料

* return          : 0表示可以繪製了,1表示圖案不變*/intscreen_draw_snow(structscreen* scr)

{

    // 靜態變數,預設初始化為0,每次都共用staticint__speed =0;

    int idx;

    if(++__speed != _INT_VSNOW)

        return1;

    //下面 就是 到了雪花飄落的時刻了 既 __speed == _INT_VSNOW__speed =0;

    //這裡重新構建雪花介面,先構建頭部,再從尾部開始構建for(idx = scr->height -1; idx >0; --idx)

        __snow_next(scr, idx);

    //構建頭部__snow_head(scr->pix, scr->width);

    return0;

}

繪製了一個螢幕物件的雪花. __speed記錄繪製次數, _INT_VSNOW控制繪製速率

d的實現程式碼如下

首先實現一個銷燬螢幕程式碼和繪製程式碼

//buf 儲存scr 中pix 資料,構建後為 (width+1)*height, 後面巨集是雪花圖案#define_CHAR_SNOW '*'staticvoid__flash_snow_buffer(structscreen* scr,char* buf)

{

    int i, j, rt;

    intheight = scr->height, width = scr->width;

    intfrate = scr->frate;//重新整理的幀頻率   

                            //每次都等一下for (;;sleep_ms(frate)) {

        //開始繪製螢幕rt = screen_draw_snow(scr);

        if (rt)

            continue;

        for(i =0;i

            char* snow = scr->pix + i*width;

            for(j =0; j

                buf[rt++] = snow[j] ? _CHAR_SNOW :'';

            buf[rt++] ='\n';

        }

        buf[rt -1] ='\0';

        //正式繪製到螢幕上        puts(buf);

        //清空老螢幕,螢幕游標回到最上面        __curup(height);

    }

}#undef_CHAR_SNOW

這裡sleep_ms(frate);是等待時間,否則太快,人眼看不見.

繪製原理是讓螢幕轉成控制檯能夠認識的字元.塞入到buf中.

__curup(height);讓繪製游標回到開頭.

後面還有一段程式碼實現

/**

* 螢幕繪製動畫效果, 繪製雪花動畫

*

* struct screen* : 螢幕結構指標*/voidscreen_flash_snow(structscreen* scr)

{

    char* buf = NULL;

    // 初始化隨機數種子,改變雪花軌跡    srand((unsigned)time(NULL));

    buf =malloc(sizeof(char)*(scr->width +1)*scr->height);

    if(NULL == buf) {

        cerr("[FATAL]Out of memory!");

        exit(EXIT_FAILURE);

    }

    __flash_snow_buffer(scr, buf);

    //1.這裡理論上不會執行到這,沒加控制器. 2.對於buf=NULL,這種程式碼 可以省掉,看程式設計習慣free(buf);

    buf = NULL;

}

這種雙函式實現一個功能技巧用的也很多.例如寫快速排序程式碼,就是這樣.

到這裡我們設計和實現都完成了.

2.程式碼效果展示

2.1window上展示

使用VS新建一個控制檯專案,F5就可以了效果如下


是動態的.

2.2對於Linux

直接使用

gcc -g -Wall snow.c -o snow.out./snow.out

執行效果如下


到這裡,C語言實現雪花效果就如上了.

2.3完整的程式碼展示.感謝有你,一路同行.

#include #include #include #include /** 時間 : 2015年12月26日11:43:22

* 描述 : 應該算過節吧,今天,寫了個雪花特效 程式碼,

*            送給 大學啟蒙 蘆老師

*                        學生王志 祝福上

* *//** 清除螢幕的shell 命令/控制檯命令,還有一些依賴平臺的實現

* 如果定義了 __GNUC__ 就假定是 使用gcc 編譯器,為Linux平臺

*    否則 認為是 Window 平臺*/#ifdefined(__GNUC__)//下面是依賴 Linux 實現#include #definesleep_ms(m) \        usleep(m *1000)//向上移動游標函式 Linuxstaticvoid__curup(int height)

{

    inti = -1;

    while(++i

        printf("\033[1A");//先回到上一行  }#else// 建立等待函式 1s 60 幀 相當於 16.7ms => 1幀, 我們取16ms// 咱麼的這螢幕 推薦 1s 25幀吧 40ms// 這裡建立等待函式 以毫秒為單位 , 需要依賴作業系統實現#include #definesleep_ms(m) \        Sleep(m)//向上移動游標staticvoid__curup(int height)

{

    COORD cr = {0,0};

    // GetStdHandle(STD_OUTPUT_HANDLE) 獲取螢幕物件, 設定游標    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cr);

}#endif/*__GNUC__ 跨平臺的程式碼都很醜陋 */// 定義初始螢幕的寬高畫素巨集#define_INT_WIDTH        (100)#define_INT_HEIGHT        (50)// 螢幕重新整理幀的速率#define_INT_FRATE        (40)// 雪花飄落的速率,相對於 螢幕重新整理幀 的倍數#define_INT_VSNOW        (10)/** 錯誤處理巨集,msg必須是""括起來的字串常量

* __FILE__        : 檔案全路徑

* __func__        : 函式名

* __LINE__        : 行數行

* __VA_ARGS__    : 可變引數巨集,

* ##表示直接連線, 例如 a##b <=> ab*/#definecerr(msg,...) \    fprintf(stderr, "[%s:%s:%d]"msg"\n",__FILE__,__func__,__LINE__,##__VA_ARGS__);/**  螢幕結構體, 具有 寬高

*  frate  : 繪製一幀的週期, 單位是 毫秒

*  width  : 螢幕的寬,基於視窗的左上角(0,0)

*  height : 螢幕的高

*  pix    : 用一維模擬二維 主要結構如下

*            0 0 0 1 0 0 1 0 1 0

*            0 1 0 1 0 1 0 1 2 0

*            . . .

*            => 0表示沒畫素, 1表示1個畫素,2表示2個畫素....*/struct screen {

    intfrate;// 也可以用 unsigned 結構int width;

    int height;

    char*pix;

};/** 建立一個 螢幕結構指標 返回

*

* int frate    : 繪製一幀的週期

* int width    : 螢幕寬度

* int height    : 螢幕高度

* return        : 指向螢幕結構的指標

* */structscreen* screen_create(intfrate,intwidth,int height);/** 銷燬一個 螢幕結構指標, 併為其置空

* struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的

* */voidscreen_destory(structscreen** pscr);/**

* 螢幕繪製函式,主要生成一個雪花效果

*

* struct screen* : 螢幕資料

* return          : 0表示可以繪製了,1表示圖案不變*/intscreen_draw_snow(structscreen* scr);/**

* 螢幕繪製動畫效果, 繪製雪花動畫

*

* struct screen* : 螢幕結構指標*/voidscreen_flash_snow(structscreen* scr);// 主函式,主業務在此執行intmain(intargc,char*argv[])

{

    structscreen* scr = NULL;

    //建立一個螢幕物件scr = screen_create(_INT_FRATE, _INT_WIDTH, _INT_HEIGHT);

    if(NULL == scr)

        exit(EXIT_FAILURE);

    //繪製雪花動畫    screen_flash_snow(scr);

    //銷燬這個螢幕物件screen_destory(&scr);

    return0;

}/** 建立一個 螢幕結構指標 返回

*

* int frate    : 繪製一幀的週期

* int width    : 螢幕寬度

* int height    : 螢幕高度

* return        : 指向螢幕結構的指標

* */structscreen*    screen_create(intfrate,intwidth,int height)

{

    structscreen *scr = NULL;

    if(frate<0|| width <=0|| height <=0) {

        cerr("[WARNING]check is frate<0 || width<=0 || height<=0 err!");

        return NULL;

    }

    //後面是 為 scr->pix 分配的記憶體 width*heightscr =malloc(sizeof(structscreen) +sizeof(char)*width*height);

    if(NULL == scr) {

        cerr("[FATALG]Out of memory!");

        return NULL;

    }

    scr->frate = frate;

    scr->width = width;

    scr->height = height;

    //減少malloc次數,malloc消耗很大,記憶體洩露呀,記憶體碎片呀scr->pix = ((char*)scr) +sizeof(struct screen);

    return scr;

}/** 銷燬一個 螢幕結構指標, 併為其置空

* struct screen** : 指向 螢幕結構指標的指標, 二級銷燬一級的

* */voidscreen_destory(structscreen** pscr)

{

    if(NULL == pscr || NULL == *pscr)

        return;

    free(*pscr);

    // 避免野指標*pscr = NULL;

}//構建開頭 的雪花,下面巨集表示每 _INT_SHEAD 個步長,一個雪花,需要是2的冪//static 可以理解為 private, 巨集,位操作程式碼多了確實難讀#define_INT_SHEAD (1<<2)staticvoid__snow_head(char* snow,int len)

{

    intr =0;

    //資料需要清空memset(snow,0, len);

    for (;;) {

        //取餘一個技巧 2^3 - 1 = 7 => 111 , 並就是取餘數intt = rand() & (_INT_SHEAD -1);

        if(r + t >= len)

            break;

        snow[r + t] =1;

        r += _INT_SHEAD;

    }

}#undef_INT_SHEAD//通過 上一個 scr->pix[scr->width*(idx-1)] => scr->pix[scr->width*idx]//下面的巨集 規定 雪花左右搖擺 0 向左一個畫素, 1 表示 不變, 2表示向右一個畫素#define_INT_SWING (3)staticvoid__snow_next(structscreen* scr,int idx)

{

    intwidth = scr->width;

    char* psnow = scr->pix + width*(idx -1);

    char* snow = psnow + width;

    inti, j, t;// i索引, j儲存下一個瞬間雪花的位置,t 臨時補得,解決雪花重疊問題

                //為當前行重置memset(snow,0, width);

    //通過上一次雪花位置 計算下一次雪花位置for(i =0; i

        for(t = psnow[i]; t>0; --t) {// 雪花可以重疊

                                      // rand()%_INT_SWING - 1 表示 雪花 橫軸的偏移量,相對上一次位置j = i + rand() % _INT_SWING -1;

            j = j<0? width -1: j >= width ?0: j;// j如果越界了,左邊越界讓它到右邊,右邊越界到左邊++snow[j];

        }

    }

}/**

* 螢幕繪製函式,主要生成一個雪花效果

*

* struct screen* : 螢幕資料

* return          : 0表示可以繪製了,1表示圖案不變*/intscreen_draw_snow(structscreen* scr)

{

    // 靜態變數,預設初始化為0,每次都共用staticint__speed =0;

    int idx;

    if(++__speed != _INT_VSNOW)

        return1;

    //下面 就是 到了雪花飄落的時刻了 既 __speed == _INT_VSNOW__speed =0;

    //這裡重新構建雪花介面,先構建頭部,再從尾部開始構建for(idx = scr->height -1; idx >0; --idx)

        __snow_next(scr, idx);

    //構建頭部__snow_head(scr->pix, scr->width);

    return0;

}//buf 儲存scr 中pix 資料,構建後為 (width+1)*height, 後面巨集是雪花圖案#define_CHAR_SNOW '*'staticvoid__flash_snow_buffer(structscreen* scr,char* buf)

{

    int i, j, rt;

    intheight = scr->height, width = scr->width;

    intfrate = scr->frate;//重新整理的幀頻率   

                            //每次都等一下for (;;sleep_ms(frate)) {

        //開始繪製螢幕rt = screen_draw_snow(scr);

        if (rt)

            continue;

        for(i =0;i

            char* snow = scr->pix + i*width;

            for(j =0; j

                buf[rt++] = snow[j] ? _CHAR_SNOW :'';

            buf[rt++] ='\n';

        }

        buf[rt -1] ='\0';

        //正式繪製到螢幕上        puts(buf);

        //清空老螢幕,螢幕游標回到最上面        __curup(height);

    }

}#undef_CHAR_SNOW/**

* 螢幕繪製動畫效果, 繪製雪花動畫

*

* struct screen* : 螢幕結構指標*/voidscreen_flash_snow(structscreen* scr)

{

    char* buf = NULL;

    // 初始化隨機數種子,改變雪花軌跡    srand((unsigned)time(NULL));

    buf =malloc(sizeof(char)*(scr->width +1)*scr->height);

    if(NULL == buf) {

        cerr("[FATAL]Out of memory!");

        exit(EXIT_FAILURE);

    }

    __flash_snow_buffer(scr, buf);

    //1.這裡理論上不會執行到這,沒加控制器. 2.對於buf=NULL,這種程式碼 可以省掉,看程式設計習慣free(buf);

    buf = NULL;

}

 

後記

到這裡就結束了,這次分享的比較簡單,有興趣的同學可以看看,推薦寫一遍.程式碼看不懂的時候,多歇歇,看得懂的時候,多寫寫,

就有套路了.歡迎吐槽.錯誤是在所難免的.

這個冬天,雪花很美,(。⌒∇⌒)