一、項(xiàng)目介紹
這是我曾經(jīng)在某個(gè)科普書(shū)上看到過(guò)的內(nèi)容。在那本書(shū)當(dāng)中有一個(gè)關(guān)于世界本源的討論,即我們目前發(fā)現(xiàn)的物理定律,是否可能是更基礎(chǔ)的模塊的宏觀呈現(xiàn)?因此,那本書(shū)提到了這個(gè)游戲,即設(shè)定一個(gè)簡(jiǎn)單的世界法則,以及對(duì)應(yīng)的初始條件,然后觀察它的演化,看看是否能呈現(xiàn)出某種更復(fù)雜的東西。
這個(gè)世界的法則十分簡(jiǎn)單。所有物質(zhì)分為活細(xì)胞和死細(xì)胞,分別表示為綠色以及灰黑色。如果上一刻,一個(gè)活細(xì)胞周圍8格的活細(xì)胞多余3個(gè)或少于2個(gè),那么它就會(huì)死去;如果一個(gè)死細(xì)胞周圍恰好有3個(gè)活細(xì)胞,那么就會(huì)在此誕生一個(gè)新的活細(xì)胞。設(shè)定了這樣的法則之后,我們就可以觀察這個(gè)世界的演化了。
編譯環(huán)境:visual c++ 6.0
第三方庫(kù):Easyx2022 注意需要提前安裝easyX,如沒(méi)有基礎(chǔ)可以先了解easyX圖形編程
二、運(yùn)行截圖
三、源碼解析
程序的整體邏輯十分簡(jiǎn)單。
初始化;
然后開(kāi)始循環(huán);
如果按下1-9,調(diào)節(jié)游戲速度;
如果按下s,產(chǎn)生一個(gè)方形分布世界,重新開(kāi)始;
如果按下r,產(chǎn)生一個(gè)隨機(jī)分布世界,重新開(kāi)始;
進(jìn)行一回合的演化,繪制圖像;
睡眠一定時(shí)間,繼續(xù)循環(huán);
游戲結(jié)束。
變量和函數(shù)也不多:
// 定義全局變量 __int8 world[102][102] = {0}; // 定義二維世界 IMAGE imgLive, imgEmpty; // 定義活細(xì)胞和無(wú)細(xì)胞區(qū)域的圖案 // 函數(shù)聲明 void Init(); // 初始化 void SquareWorld(); // 創(chuàng)建一個(gè)細(xì)胞以方形分布的世界 void RandWorld(); // 創(chuàng)建一個(gè)細(xì)胞隨機(jī)分布的世界 void PaintWorld(); // 繪制世界 void Evolution(); // 進(jìn)化
接下來(lái)我們看每個(gè)函數(shù)的實(shí)行:
// 初始化 void Init() { // 創(chuàng)建繪圖窗口 initgraph(640,480); // 設(shè)置隨機(jī)種子 srand((unsigned)time(NULL)); // 調(diào)整世界圖案的大小 Resize(&imgLive, 4, 4); Resize(&imgEmpty, 4, 4); // 繪制有生命世界的圖案 SetWorkingImage(&imgLive); setcolor(GREEN); setfillstyle(GREEN); fillellipse(0, 0, 3, 3); // 繪制無(wú)生命世界的圖案 SetWorkingImage(&imgEmpty); setcolor(DARKGRAY); rectangle(1, 1, 2, 2); // 恢復(fù)對(duì)默認(rèn)窗口的繪圖 SetWorkingImage(NULL); // 輸出簡(jiǎn)單說(shuō)明 setfont(24, 0, "黑體"); outtextxy(254, 18, "生 命 游 戲"); RECT r = {440, 60, 620, 460}; setfont(12, 0, "宋體"); drawtext("生命游戲簡(jiǎn)介:\n, &r, DT_WORDBREAK); // 產(chǎn)生默認(rèn)的細(xì)胞以方形分布的世界 SquareWorld(); }
initgraph用于初始化繪圖窗口。在頭文件Easyx當(dāng)中,后面的函數(shù)如果沒(méi)有說(shuō)明,默認(rèn)也是在Easyx當(dāng)中的。
Resize用于調(diào)整指定繪圖設(shè)備的尺寸,避免繪制圖像過(guò)大。
然后繪制有生命的圖案和無(wú)生命的圖案,其原理類似:
SetWorkingImage用于設(shè)定當(dāng)前的繪圖設(shè)備,將數(shù)據(jù)寫入對(duì)應(yīng)地址位置。
setcolor用于設(shè)置繪圖前景色,setfillstyle用于設(shè)置當(dāng)前設(shè)備填充樣式。
fillellipse這個(gè)函數(shù)用于畫(huà)有邊框的填充橢圓。
接下來(lái)輸出說(shuō)明:
setfont設(shè)置字體
outtextxy用于在特定位置輸出文字
RECT r用于為drawtext設(shè)定一個(gè)指定矩形區(qū)域的指針
drawtext這個(gè)函數(shù)用于在指定區(qū)域內(nèi)以指定格式輸出字符串。
然后是兩個(gè)創(chuàng)建世界的函數(shù)。
// 創(chuàng)建一個(gè)細(xì)胞以方形分布的世界 void SquareWorld() { memset(world, 0, 102 * 102 * sizeof(__int8)); for(int x = 1; x <= 100; x++) world[x][1] = world[x][100] = 1; for(int y = 1; y <= 100; y++) world[1][y] = world[100][y] = 1; } // 創(chuàng)建一個(gè)細(xì)胞隨機(jī)分布的世界 void RandWorld() { for(int x = 1; x <= 100; x++) for(int y = 1; y <= 100; y++) world[x][y] = rand() % 2; }
利用二層循環(huán)遍歷world[x][y]當(dāng)中每個(gè)數(shù)據(jù),將其調(diào)整到想要的值。隨機(jī)世界將每個(gè)細(xì)胞的狀態(tài)設(shè)置為隨機(jī)數(shù)除以2的余數(shù),保證每次產(chǎn)生的都是新的世界。
然后是繪制世界圖像。
// 繪制世界 void PaintWorld() { for(int x = 1; x <= 100; x++) for(int y = 1; y <= 100; y++) putimage(16 + x * 4, 56 + y * 4, world[x][y] ? &imgLive : &imgEmpty); }
利用雙層循環(huán),將每個(gè)點(diǎn)的狀態(tài)所對(duì)應(yīng)圖像打印出來(lái)。
最后是世界的演化函數(shù),從世界的當(dāng)前狀態(tài)推出世界下一刻。
// 進(jìn)化 void Evolution() { __int8 tmp[102][102] = {0}; // 臨時(shí)數(shù)組 int sum; for(int x = 1; x <= 100; x++) { for(int y = 1; y <= 100; y++) { // 計(jì)算周圍活著的生命數(shù)量 sum = world[x+1][y] + world[x+1][y-1] + world[x][y-1] + world[x-1][y-1] + world[x-1][y] + world[x-1][y+1] + world[x][y+1] + world[x+1][y+1]; // 計(jì)算當(dāng)前位置的生命狀態(tài) switch(sum) { case 3: tmp[x][y] = 1; break; case 2: tmp[x][y] = world[x][y]; break; default: tmp[x][y] = 0; break; } } } // 將臨時(shí)數(shù)組恢復(fù)為世界 memcpy(world, tmp, 102 * 102 * sizeof(__int8)); }
我們只需要定義一個(gè)臨時(shí)數(shù)組,根據(jù)當(dāng)前的世界狀態(tài)推斷就可以了。計(jì)算某個(gè)坐標(biāo)周圍8格活著的生命數(shù)量,得出該點(diǎn)的狀態(tài)。注意數(shù)組的邊界。
之后用memcpy將臨時(shí)數(shù)據(jù)輸入world變量當(dāng)中。
memcpy是<string.h>當(dāng)中的內(nèi)存拷貝函數(shù),可用于復(fù)制任何種類的數(shù)據(jù)。
// 主函數(shù) int main() { Init(); int Speed = 500; // 游戲速度(毫秒) while(true) { if (kbhit() || Speed == 900) { char c = getch(); if (c == ' ' && Speed != 900) c = getch(); if (c >= '0' && c <= '9') Speed = ('9' - c) * 100; switch(c) { case 's': case 'S': SquareWorld(); // 產(chǎn)生默認(rèn)的細(xì)胞以方形分布的世界 break; case 'r': case 'R': RandWorld(); // 產(chǎn)生默認(rèn)的細(xì)胞隨機(jī)分布的世界 break; case VK_ESCAPE: goto END; } } Evolution(); // 進(jìn)化 PaintWorld(); // 繪制世界 if (Speed != 900) // 速度為 900 時(shí),為按任意鍵單步執(zhí)行 Sleep(Speed); } END: closegraph(); return 0; }
最后放一個(gè)主函數(shù)。
函數(shù) kbhit()函數(shù)的作用是檢查控制臺(tái)窗口的按鍵是否被按下。
closegraph()用于關(guān)閉繪圖區(qū)域。
四、完整源碼
C語(yǔ)言網(wǎng)提供由在職研發(fā)工程師或ACM藍(lán)橋杯競(jìng)賽優(yōu)秀選手錄制的視頻教程,并配有習(xí)題和答疑,點(diǎn)擊了解:
一點(diǎn)編程也不會(huì)寫的:零基礎(chǔ)C語(yǔ)言學(xué)練課程
解決困擾你多年的C語(yǔ)言疑難雜癥特性的C語(yǔ)言進(jìn)階課程
從零到寫出一個(gè)爬蟲(chóng)的Python編程課程
只會(huì)語(yǔ)法寫不出代碼?手把手帶你寫100個(gè)編程真題的編程百練課程
信息學(xué)奧賽或C++選手的 必學(xué)C++課程
藍(lán)橋杯ACM、信息學(xué)奧賽的必學(xué)課程:算法競(jìng)賽課入門課程
手把手講解近五年真題的藍(lán)橋杯輔導(dǎo)課程