這個(gè)東西以前在看知乎的時(shí)候就看到過挣磨,感覺挺好玩的。最近又看到了荤懂,細(xì)細(xì)看了一下原理,恍然大悟這不就是一個(gè)空域?yàn)V波么塘砸?寫一個(gè)應(yīng)該很好玩吧节仿?于是就動(dòng)手了,為了顯示方便用的Opencv的Mat數(shù)據(jù)結(jié)構(gòu)來存取數(shù)據(jù)和顯示掉蔬。寫了一下午差不多就可以了廊宪,后面再加了些配置文件的接口,并給了一些配置文件女轿,這里記錄一下箭启。
程序:https://github.com/zhxing001/LifeGame
1.生命游戲
生命游戲也叫康威游戲,是一種細(xì)胞自動(dòng)機(jī)蛉迹,最初是由數(shù)學(xué)家約翰·何頓·康威在1970年發(fā)明的傅寡。
這個(gè)游戲是一個(gè)零玩家游戲,整個(gè)游戲會(huì)根據(jù)定義的規(guī)則自動(dòng)執(zhí)行下去。
生命游戲的游戲場(chǎng)地是一個(gè)二維的棋盤荐操,每一個(gè)位置叫做一個(gè)細(xì)胞芜抒,有生
, 死
兩種狀態(tài),如果相鄰方格活著的細(xì)胞數(shù)量過多托启,這個(gè)細(xì)胞會(huì)因?yàn)?code>資源匱乏而死亡宅倒,相反,如果因?yàn)橹車募?xì)胞過少屯耸,這個(gè)細(xì)胞會(huì)因?yàn)樘聠味廊ス涨ā?shí)際中,這種規(guī)則是可以自定義的疗绣。有一點(diǎn)要注意:棋牌上的所有細(xì)胞同時(shí)刷新狀態(tài)线召。一個(gè)細(xì)胞生死變化不立即影響其他細(xì)胞,在這種規(guī)則下持痰,雜亂無序的的細(xì)胞會(huì)逐漸演化出各種精致灶搜,有型的結(jié)構(gòu)。
有個(gè)軟件工窍,內(nèi)置了各種規(guī)則以及初始狀態(tài)割卖,也不大,可以下載下來玩一下:golly主頁患雏,主頁上的動(dòng)圖感受一下鹏溯,這是一種比較復(fù)雜的初始狀態(tài)了。還有一個(gè)網(wǎng)址可以在線玩:https://playgameoflife.com/
我采取的是最原始的規(guī)則:(一個(gè)點(diǎn)周圍的8個(gè)點(diǎn)為8鄰域)
- 1. 如果一個(gè)細(xì)胞周圍有3個(gè)細(xì)胞為生(一個(gè)細(xì)胞周圍共有8個(gè)細(xì)胞)淹仑,則該細(xì)胞為生(即該細(xì)胞若原先為死丙挽,則轉(zhuǎn)為生,若原先為生匀借,則保持不變) 颜阐。
- 2. 如果一個(gè)細(xì)胞周圍有2個(gè)細(xì)胞為生,則該細(xì)胞的生死狀態(tài)保持不變吓肋;
- 3. 在其它情況下凳怨,該細(xì)胞為死(即該細(xì)胞若原先為生,則轉(zhuǎn)為死是鬼,若原先為死肤舞,則保持不變)
利用這個(gè)規(guī)則讓其自動(dòng)演化就可以了:
2. 常見種子。
-
滑翔機(jī)均蜜。
可以向右下方滑動(dòng):
-
滑翔者李剖。
每四個(gè)回合會(huì)向右走一格。
-
脈沖星囤耳。
周期為3篙顺,不斷閃爍偶芍。
結(jié)果:
-
滑翔者槍
這個(gè)玩意可以不斷的發(fā)射滑翔者。
再有其他的復(fù)雜的圖像就只能自己去發(fā)掘了慰安,還有一種方法就是隨機(jī)初始化種子: - 隨機(jī)初始化種子腋寨。
就是隨機(jī)讓一部分的細(xì)胞存活,然后執(zhí)行游戲規(guī)則化焕,有可能會(huì)產(chǎn)生出比較穩(wěn)定的狀態(tài)萄窜,當(dāng)然這個(gè)也是有研究的,結(jié)果就發(fā)現(xiàn)隨機(jī)激活37.5%的種子的時(shí)候產(chǎn)生比較穩(wěn)定圖案的概率比較大撒桨。這個(gè)我在代碼里也給了查刻,可以設(shè)置。
3. 實(shí)現(xiàn)過程凤类。
其實(shí)主要的代碼比較簡(jiǎn)單穗泵,就是空域?yàn)V波的錨點(diǎn)如何根據(jù)周圍的點(diǎn)來決定自己的狀態(tài):
- 游戲規(guī)則實(shí)現(xiàn):
void lifeGame(Mat &init_image, int loop_num, bool writeImg,int ms)
{
int rows = init_image.rows;
int cols = init_image.cols;
namedWindow("source", WINDOW_NORMAL);
imshow("source", init_image);
//k是迭代次數(shù)
namedWindow("LIFE_GAME", 2);
for (int k = 0; k < loop_num; k++)
{
cout << k << endl;
Mat tmp = Mat::zeros(rows, cols, CV_8UC1);
uchar x1, x2, x3,
x4, x6,
x7, x8, x9;
for (int i = 1; i < rows - 1; i++)
{
int count = 0;
for (int j = 1; j < cols - 1; j++)
{
x1 = init_image.at<uchar>(i - 1, j - 1);
x2 = init_image.at<uchar>(i - 1, j);
x3 = init_image.at<uchar>(i - 1, j + 1);
x4 = init_image.at<uchar>(i, j - 1);
x6 = init_image.at<uchar>(i, j + 1);
x7 = init_image.at<uchar>(i + 1, j - 1);
x8 = init_image.at<uchar>(i + 1, j);
x9 = init_image.at<uchar>(i + 1, j + 1);
count = x1 + x2 + x3
+ x4 + x6
+ x7 + x8 + x9;
//生命游戲的核心代碼,三個(gè)if代表三個(gè)規(guī)則
if (count == 255 * 3)
tmp.at<uchar>(i, j) = 255;
else if (count == 255 * 2)
tmp.at<uchar>(i, j) = init_image.at<uchar>(i, j);
else
tmp.at<uchar>(i, j) = 0; //這一句也是可以不要的谜疤,因?yàn)楸旧砭褪?
}
}
tmp.copyTo(init_image);
tmp.release();
imshow("LIFE_GAME", init_image);
if (writeImg)
imwrite("res//" + to_string(k) + ".jpg", init_image);
waitKey(ms);
}
這樣的話生成的畫布是固定大小的佃延,自己設(shè)置,有的平移類的種子出了邊界就不會(huì)再回來了夷磕,在此基礎(chǔ)上又想了一種辦法:把左右兩邊相連履肃,上下相連,這樣就可以變向的實(shí)現(xiàn)畫布放大(當(dāng)然這不是理想的解法)坐桩,另外一點(diǎn)畫布也是可以設(shè)置大一點(diǎn)的尺棋,因?yàn)樗惴ê?jiǎn)單,用C++寫出來效率還是很高的绵跷,2000*2000的圖像還是可以實(shí)現(xiàn)勉強(qiáng)實(shí)時(shí)的膘螟。
-
配置文件讀取:
配置文件以txt文件形式存儲(chǔ)碾局,然后讀入荆残,只存儲(chǔ)活著
點(diǎn)的坐標(biāo),每一行的第一個(gè)數(shù)表示該行的行坐標(biāo)净当,后面是列坐標(biāo)脊阴,比如:
1 5
2 4 5 6
3 3 4 5 6 7
4 2 3 4 5 6 7 8
5 1 2 3 4 5 6 7 8 9
6 2 3 4 5 6 7 8
7 3 4 5 6 7
8 4 5 6
9 5
對(duì)應(yīng)的圖片張這樣:
把所有的點(diǎn)移動(dòng)到左上角來定位坐標(biāo),坐標(biāo)初始位置從1開始蚯瞧。
解析的方法也比較簡(jiǎn)單,獲取每一行的數(shù)字使用getline
函數(shù)品擎,每一行獲取數(shù)字的時(shí)候使用istringstream
,具體:
void getInt(string &s, vector<Point2d> &res,int &cmax,int &rmax) //從一行中解析出整數(shù),并記錄最大行數(shù)
{
istringstream iss(s);
int num;
int cnt=0; //讀第一個(gè)數(shù)的標(biāo)志
int line; //行數(shù)
int colmax = 0;
while (iss >> num)
{
if (cnt == 0) //每一行的第一個(gè)數(shù)是行號(hào)
{
line = num;
rmax = line; //記錄行號(hào)
cnt++;
}
else //重構(gòu)坐標(biāo)存入res中
{
res.push_back(Point2d(line, num));
if (num > colmax)
{
colmax = num;
}
}
}
cmax = colmax;
}
//從txt中提取坐標(biāo)點(diǎn)埋合,并記錄最大的行和列
void getPos(string &file, vector<Point2d> &CfgMat, int &rmax,int &cmax)
{
ifstream cfg(file);
string s;
int _cmax = 0;
int _rmax = 0;
while (getline(cfg, s))
{
cout << s << endl;
getInt(s, CfgMat,_cmax,_rmax);
if (_cmax > cmax)
cmax = _cmax;
if (_rmax > rmax)
rmax = _rmax;
}
}
重構(gòu)棋盤矩陣的時(shí)候會(huì)把棋牌擴(kuò)大(根據(jù)記錄的種子的最大行和列自定義行和列的放大系數(shù))。
其他的就沒什么了萄传,在cfg文件里我存了幾個(gè)比較經(jīng)典的初始種子甚颂,可以讀取來顯示蜜猾。
4. 效果展示。
-
X
型種子振诬。
滑翔者槍
這里有點(diǎn)小蹭睡,程序里是可以調(diào)整顯示大小的: