GTK实现生命游戏小结

一个课程proj,要求在linux下写一个生命游戏,几乎没有要求,可以1-3人做,可以有gui也可以直接是命令行,可以是二维也可以是3D,先上程序效果图--->

在这之前已经很久没有碰过C了,更别说是linux了...拿到proj题目后,回来第一件事就是在电脑上装个ubuntu,目前这台小黑兢兢业业地跑着win7 Mac X还有ubuntu,虽然已经是08年的电脑,但是除了感觉硬盘有点吃紧外,3个os都运行流畅,很少卡机。进水还有浸水几次,被我拆机几次都仍旧无比坚挺,真是电脑中的战斗机。

赞美过小黑后入正题。为了不丢人,我决定做个带gui的程序,google之后发现比较主流的是qt和gtk,因为c++不太会,于是就选择了用gtk。

在gnome上看了两天后对gtk有了个大致的了解,就开始动手慢慢尝试gtk还有proj。下面分几部分对proj及gtk做相应总结。


一、安装GTK/GNOME环境及code::blocks配置

环境安装可以直接参考ubuntu wiki上的文章,不再赘述,文章链接: http://wiki.ubuntu.org.cn/Gtk%E4%B8%8EQt%E7%BC%96%E8%AF%91%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85%E4%B8%8E%E9%85%8D%E7%BD%AE

但是实际上在用vi写了几段小程序后,发现写起来不顺手,编译gtk又麻烦(需要带上`pkg-config --cflags --libs gtk+-3.0`)于是还是不要装b的好,果断下了code::blocks。因为在code::blocks中编译需要引用到了gtk库,所以我一开始还在搜索如何配置code::blocks让它搜索外部库,网上找了几篇帖子,好像都有点问题的样子。就在郁闷之际,突然发现在new proj的时候,code::blocks有gtk+ proj选项,于是就开开心心地直接选了,一试果然可以!


二、生命游戏算法

首先是生命游戏的规则介绍,摘自wiki:

生命游戏中,对于任意细胞,规则如下:
每个细胞有两种状态-存活或死亡,每个细胞与以自身为中心的周围八格细胞产生互动。(如图,黑色为存活,白色为死亡)

  1. 当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。 (模拟繁殖)
  2. 当前细胞为存活状态时,当周围低于2个(不包含2个)存活细胞时, 该细胞变成死亡状态。(模拟人口稀少)
  3. 当前细胞为存活状态时,当周围有2个或3个存活细胞时, 该细胞保持原样。
  4. 当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成死亡状态。(模拟过度拥挤)

可以把最初的细胞结构定义为种子,当所有在种子中的细胞同时被以上规则处理后, 可以得到第一代细胞图。继续让规处理当前的细胞图,可以得到下一代的细胞图,周而复始。

根据上述算法描述可知对于一个细胞来说,下一步的生死是取决于其邻居的数量,所以也就很简单,只要去算一个元素它周围活着状态的细胞数量就可以了。在具体实现中,还要判断其周围细胞是否越界,如果超出范围就不计数。然后再对数组中的每个元素进行遍历,就能得到所有元素下一步的状态,继而相应打印即可。理解了算法,我先在console下简单实现,这个很快就搞定,那么下一步就是如何在gtk上实现带有gui的生命游戏了。


三、GTK按钮、文本及布局

在GTK中,好像所有的组件都会声明成GtkWidget,因为很多函数调用的时候要求的参数格式需要是GtkWidget的,然后再用相应的方法new出各种组件,这里多少有点多态的味道。比如go按钮的创建方法如下:

GtkWidget *b_start;

b_start = gtk_button_new_with_label("GO");

GTK中的表格布局,我觉得是蛮有趣的,它的构建函数为:

GtkWidget *gtk_table_new (guint rows, guint columns, gboolean homogeneous);

其中rows和columns分别是放入组件的位置,以左上角顶点为(0,0)初始,这点和很多绘图或者坐标都类似。

在程序中,我因为用点击button的方式来初始化游戏,所以我定义了一个二维button数组,然后在将它们分别填入到table布局中,如下:

int i, j;
    for (j = 1; j < 10; ++j) {
        for (i = 0; i < 9; ++i) {

            g_button[j-1][i] = (GtkButton*)gtk_button_new();
            //         xyAxis[0] = i;
            //         xyAxis[1] = j-1;
            //         g_signal_connect(G_OBJECT(g_button[i][j-1]), "clicked", GTK_SIGNAL_FUNC (callback), xyAxis);
            gtk_table_attach_defaults(GTK_TABLE(table), (GtkWidget*)g_button[j-1][i], i, i+1, j, j+1);
        }
    }

这里我并没有删去注释部分,是因为链接每个按钮的信号让我遇到了一点阻滞,将在下面一点中做出说明。


四、按钮信号链接,傻傻的三维数组传值

正如在上文代码中显示的那样,我本来是在new button数组的时候一并为它们链接上各自的信号,我本来是认为这样ok的,因为似乎看起来真的很合理,不是么?但是程序一跑我就郁闷了,因为在点击初始化阶段完全没有问题,但是在点击GO计算下一状态并显示的时候,所有按钮标签都被清空了。加各种printf查看才发现,在初始化之后永远只有最后一个按钮的状态被标志为ALIVE,哪怕根本没有点击它。这点让我郁闷不已,因为我一开始感觉这样的链接信号方式是高效有效的,没想到结果是这样。

用中英法三种关键字去google,都没有找到gtk多按钮信号的例子,无奈之下只能自己静下心来分析。既然上面的这样链接方法不行,也就是说需要去为每个按钮链接相应的信号,但是总共我现在有9*9个按钮,没理由为每个按钮单独写两行去做信号链接,这样我会鄙视自己的智商...几经尝试之后,我最后想到了用三维数组传值,链接信号的方法。但是实际上我一点把握都没有,之前只传递过一次二维数组,只是想着试试吧,然后就去做了。

对于new出来的n*n个按钮,我有另外一个n*n的数组记录每个按钮的状态,按下按钮则为ALIVE,则在相应的数组位置置为ALIVE。这里之所以会想到要用三维数组传递是因为在gtk的g_signal_connect函数中,只有一个指针用于传递数据,但是我需要这个按钮的位置(x, y),又因为有n*n个按钮,所以我就想到了用第三维来储存数组位置,前面两维用于指示按钮。因为上面链接的方法之所以不成功的原因,我认为就是因为所有的按钮都用一个数组指示,导致链接信号不正确,所以这里我用了三维数组的方法,用前两维来分别指示按钮,实际上只用到第三维来传递按钮位置。具体相关代码如下:

void signalConnect() {

    int i,j;
    for (i=0; i<AREA_MAX_X; i++) {
        for (j=0; j<AREA_MAX_Y; j++) {

            xyAxis[i][j][0] = i;
            xyAxis[i][j][1] = j;

            g_signal_connect(G_OBJECT(g_button[i][j]), "clicked", GTK_SIGNAL_FUNC (callback), xyAxis[i][j]);

        }
    }
}

void callback( GtkWidget *widget, gpointer data ) {

    GtkButton *button = GTK_BUTTON(widget);

    int* intdata = GPOINTER_TO_INT(data);

    map[*intdata][*(intdata+1)] = ALIVE;
    printf("map %d and %d at CallBack : %d ", *intdata, *(intdata+1), map[*intdata][*(intdata+1)]);

    char labelAlive[5] = {'*'};

    gtk_button_set_label(button, labelAlive);
}

当编译通过的时候,我自己都笑了。。。因为我为这个问题已经查找网络、尝试了很多方法,前面的和后面的都早就想好了,就这里链接的地方断开了,郁闷之至。编译通过我都以为是code::blocks出问题了。。。然后一跑,发现真的是可以的!高兴过后,静下心看三维数组传递,其实还是那句话,没有多维数组,所谓多维也就是一维的延长而已。这里在传递的时候,将三维数组的前二维作为第三维的地址,然后用一个指针来接收,那么存在第三维的值也可以直接用这个指针来取到了。指针对于C以及objective-c,应该也包括c++来说真的是神器,因为使用灵活,所以无论哪里有需要都能堵上。


五、(GTK)TimeOut == (Android) schedule

算法搞定,信号链接完毕,那么就需要让它能够自动运行下去了。这种跟timer相关的循环任务执行函数,我总是叫不出口“定时器”,其实我觉得Android中的shcedule是更合适的称呼,但是直译过来好像也是怪怪的。。。

GTK中的TimeOut函数定义为:guint gtk_timeout_add(guint32 interval, GtkFunction function, gpointer data);

其中函数返回的guint是用于标志该TimeOut函数的tag,通过该标志,可以调用 void gtk_timeout_remove(gunit tag); 来停止这个TimeOut函数,也就是在pause和restart按钮中需要的功能。在具体程序中,我将TimeOut的返回标志设为9,调用方法为:

signTimer = g_timeout_add(1000,(GSourceFunc)handler, NULL);

就像上面说的,要停止它只需要调用 gtk_timeout_remove(signTimer); 就ok了。


至此这篇小结就差不多这样了,之前占位的那篇(系列)IOS项目的小结还需要欠一段时间,可能要等到圣诞假的时候才有时间更新了...考试季+3个proj迫在眉睫...有心无力了。


day day up, good good study:)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值