2.LifeGame生命游戏

这个东西以前在看知乎的时候就看到过,感觉挺好玩的。最近又看到了,细细看了一下原理,恍然大悟这不就是一个空域滤波么?写一个应该很好玩吧?于是就动手了,为了显示方便用的Opencv的Mat数据结构来存取数据和显示。写了一下午差不多就可以了,后面再加了些配置文件的接口,并给了一些配置文件,这里记录一下。

程序:https://github.com/zhxing001/LifeGame

1.生命游戏

生命游戏也叫康威游戏,是一种细胞自动机,最初是由数学家约翰·何顿·康威在1970年发明的。

这个游戏是一个零玩家游戏,整个游戏会根据定义的规则自动执行下去。

生命游戏的游戏场地是一个二维的棋盘,每一个位置叫做一个细胞,有, 两种状态,如果相邻方格活着的细胞数量过多,这个细胞会因为资源匮乏而死亡,相反,如果因为周围的细胞过少,这个细胞会因为太孤单而死去。实际中,这种规则是可以自定义的。有一点要注意:棋牌上的所有细胞同时刷新状态。一个细胞生死变化不立即影响其他细胞,在这种规则下,杂乱无序的的细胞会逐渐演化出各种精致,有型的结构。

有个软件,内置了各种规则以及初始状态,也不大,可以下载下来玩一下:golly主页,主页上的动图感受一下,这是一种比较复杂的初始状态了。还有一个网址可以在线玩:https://playgameoflife.com/

ticker.gif

我采取的是最原始的规则:(一个点周围的8个点为8邻域)

  • 1. 如果一个细胞周围有3个细胞为生(一个细胞周围共有8个细胞),则该细胞为生(即该细胞若原先为死,则转为生,若原先为生,则保持不变) 。
  • 2. 如果一个细胞周围有2个细胞为生,则该细胞的生死状态保持不变;
  • 3. 在其它情况下,该细胞为死(即该细胞若原先为生,则转为死,若原先为死,则保持不变)

利用这个规则让其自动演化就可以了:

2. 常见种子。

  • 滑翔机。
    可以向右下方滑动:


  • 滑翔者。



    每四个回合会向右走一格。

  • 脉冲星。
    周期为3,不断闪烁。



    结果:


  • 滑翔者枪



    这个玩意可以不断的发射滑翔者。
    再有其他的复杂的图像就只能自己去发掘了,还有一种方法就是随机初始化种子:

  • 随机初始化种子。
    就是随机让一部分的细胞存活,然后执行游戏规则,有可能会产生出比较稳定的状态,当然这个也是有研究的,结果就发现随机激活37.5%的种子的时候产生比较稳定图案的概率比较大。这个我在代码里也给了,可以设置。

3. 实现过程。

其实主要的代码比较简单,就是空域滤波的锚点如何根据周围的点来决定自己的状态:

  • 游戏规则实现:
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是迭代次数
    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;
                //生命游戏的核心代码,三个if代表三个规则
                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;         //这一句也是可以不要的,因为本身就是0
            }
        }
        tmp.copyTo(init_image);
        tmp.release();
        imshow("LIFE_GAME", init_image);
        if (writeImg)
            imwrite("res//" + to_string(k) + ".jpg", init_image);
        waitKey(ms);
    }

这样的话生成的画布是固定大小的,自己设置,有的平移类的种子出了边界就不会再回来了,在此基础上又想了一种办法:把左右两边相连,上下相连,这样就可以变向的实现画布放大(当然这不是理想的解法),另外一点画布也是可以设置大一点的,因为算法简单,用C++写出来效率还是很高的,2000*2000的图像还是可以实现勉强实时的。

  • 配置文件读取:
    配置文件以txt文件形式存储,然后读入,只存储活着点的坐标,每一行的第一个数表示该行的行坐标,后面是列坐标,比如:
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

对应的图片张这样:


把所有的点移动到左上角来定位坐标,坐标初始位置从1开始。

解析的方法也比较简单,获取每一行的数字使用getline函数,每一行获取数字的时候使用istringstream,具体:

void getInt(string &s, vector<Point2d> &res,int &cmax,int &rmax)  //从一行中解析出整数,并记录最大行数
{
    istringstream iss(s);
    
    int num;
    int cnt=0;       //读第一个数的标志
    int line;        //行数
    int colmax = 0;
    while (iss >> num)
    {
        if (cnt == 0)     //每一行的第一个数是行号
        {
            line = num;
            rmax = line;       //记录行号
            cnt++;
        }
        else             //重构坐标存入res中
        {
            res.push_back(Point2d(line, num));
            if (num > colmax)
            {
                colmax = num;
            }
        }
    }
    cmax = colmax;
}
//从txt中提取坐标点,并记录最大的行和列
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;
    } 
}

重构棋盘矩阵的时候会把棋牌扩大(根据记录的种子的最大行和列自定义行和列的放大系数)。
其他的就没什么了,在cfg文件里我存了几个比较经典的初始种子,可以读取来显示。

4. 效果展示。

  • X型种子。

  • 滑翔者枪
    这里有点小,程序里是可以调整显示大小的:

test.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349