OpenCv程序员生命游戏

2.LifeGame生命游戏

2018-12-28  本文已影响21人  和蔼的zhxing

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

1.生命游戏

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

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

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

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

ticker.gif

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

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

2. 常见种子。

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的图像还是可以实现勉强实时的。

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. 效果展示。

test.gif
上一篇 下一篇

猜你喜欢

热点阅读