jjzjj

100行python代码实现细胞自动机(康威生命游戏)

阿橙学长 2024-02-05 原文

 英国数学家约翰·何顿·康威在1970年发明了细胞自动机,它属于一种仿真程序,通过设定一些基本的规则来模拟和显示的图像的自我进化,看起来颇似生命的出生和繁衍过程,故称为“生命游戏”。

完成效果

用到的第三方库

pygame

基本规则

康威生命游戏在网格上进行,有填充的网格代表有生命,或理解成一个细胞,游戏规则只有四条:

1 当周围仅有1个或没有存活细胞时, 原来的存活细胞进入死亡状态。(细胞过于稀少)

2 当周围有2个或3个存活细胞时, 网格保持原样。

3 当周围有4个及以上存活细胞时,原来的存活细胞亦进入死亡状态。(细胞过于拥挤)

4 当周围有3个存活细胞时,空白网格变成存活细胞。(繁殖新细胞)

代码实现

首先定义两个常量,来代表一个细胞(网格)的生或空白的状态:

ALIVE = (124, 252, 0)  # 绿色
EMPTY = (0, 0, 0)      # 黑色

我这里取了个巧,直接用RGB颜色来表示细胞生存或者死亡这两种状态,因为在后面的pygame的展示中,ALIVE的细胞用绿色表示,EMPTY的区域用黑色表示。

下面几个变量是pygame里用到的参数,分别是屏幕的尺寸,x和y方向的网格数量,还有单个细胞的尺寸:

SCREEN_WIDHT = 600
SCREEN_HEIGHT = 600
X = 100   # X方向的网格数量
Y = 100   # Y方向的网格数量
CELL_WIDTH = SCREEN_WIDHT / X
CELL_HEIGHT = SCREEN_HEIGHT / Y

现在来定义一个细胞,也就是一个网格:

import pygame
from pygame.locals import *


class Cell:
    '''单个细胞'''
    def __init__(self, x, y):
        self.state = EMPTY
        self.rect = Rect(x * CELL_WIDTH, y * CELL_HEIGHT, 
                         CELL_WIDTH, CELL_HEIGHT)

    def draw(self, screen):
        pygame.draw.rect(screen, self.state, self.rect)

细胞的属性很简单,state代表当前状态,我们默认每个细胞初始都是死亡状态;rect属性是用pygame里的Rect对象构建的,表示一个矩形区域。最后有一个方法draw,能够将自身“画”到对应的screen上。

 接下来定义整个网格:

class Grid:
    def __init__(self, X, Y):
        self.X = X
        self.Y = Y
        self.rows = []
        for y in range(Y):
            self.rows.append([])
            for x in range(X):
                self.rows[y].append(Cell(x, y))

    def get_state(self, y, x):
        return self.rows[y % self.Y][x % self.X].state

    def set_state(self, y, x, state):
        self.rows[y % self.Y][x % self.X].state = state

    def draw(self, screen):
        for row in self.rows:
            for cell in row:
                cell.draw(screen)

网格对象的核心是他的rows属性,这是一个二维列表,列表中的每个位置都是一个细胞对象,可以通过坐标(x, y)定位到。另外定义了三个方法,get_state和set_state用来获取和改变某个坐标中的细胞的状态,这里要注意一下,因为细胞自动机会自发扩散进化,所以会出现超出列表长度的情况(就是超出屏幕导致报错),所以列表的下标没有简单的用x,y,而是做成了可以折返的效果。

以下两个模块级函数用于实现生命游戏的逻辑:

def count_neighbors(y, x, get_state):
    n_ = get_state(y - 1, x + 0)  # North
    ne = get_state(y - 1, x + 1)  # Northeast
    e_ = get_state(y + 0, x + 1)  # East
    se = get_state(y + 1, x + 1)  # Southeast
    s_ = get_state(y + 1, x + 0)  # South
    sw = get_state(y + 1, x - 1)  # Southwest
    w_ = get_state(y + 0, x - 1)  # West
    nw = get_state(y - 1, x - 1)  # Northwest
    neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw]
    count = 0
    for state in neighbor_states:
        if state == ALIVE:
            count += 1
    return count

def next_state(state, neighbors):
    if state == ALIVE:
        if neighbors < 2:
            return EMPTY
        elif neighbors > 3:
            return EMPTY
    else:
        if neighbors == 3:
            return ALIVE
    return state

count_neighbors函数接收一个坐标和一个获取状态的函数,用来计算该坐标相邻的邻居坐标有多少个存活的细胞;next_state函数描述了生命游戏的核心规则,它接收细胞当前状态和周边邻居坐标中存活的细胞数量,输出下一个状态。有了这两个函数,就可以写单个细胞以及整个网格状态变化的逻辑了。

下面两个模块级函数是设置单个细胞和整个网格的新状态

def step_cell(y, x, get_state, set_state):
    state = get_state(y, x)
    neighbors = count_neighbors(y, x, get_state)
    new_state = next_state(state, neighbors)
    set_state(y, x, new_state)

def simulate(grid):
    new_grid = Grid(grid.X, grid.Y)
    for y in range(grid.Y):
        for x in range(grid.X):
            step_cell(y, x, grid.get_state, new_grid.set_state)
    return new_grid

其中,step_cell用来设置下一次细胞的状态,simulate用来返回下一代的网格。

主体代码都已经写好,下面开始测试了:

if __name__ == "__main__":
    # pygame初始化的相关内容
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDHT, SCREEN_HEIGHT))
    pygame.display.set_caption('Game Of Live')
    framerate = pygame.time.Clock()

    # 设定网格的一个初始状态
    grid = Grid(X, Y)
    grid.set_state(2, 4, ALIVE)
    grid.set_state(2, 2, ALIVE)
    grid.set_state(3, 3, ALIVE)
    grid.set_state(3, 4, ALIVE)
    grid.set_state(4, 4, ALIVE)

    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

        grid.draw(screen)      # 将网格画到屏幕上
        grid = simulate(grid)  # 获得下一代网格

        pygame.display.update()
        framerate.tick(10)     # 设置每秒10帧

以上实现生命游戏的代码应该是非常简洁清晰的,代码一共也就100行左右,而且只要学一些pygame这个库的最基础知识,就可以实现这个非常神奇的效果。

有关100行python代码实现细胞自动机(康威生命游戏)的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  5. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  6. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  7. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  8. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  9. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  10. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

随机推荐