21、遗传编程与康威生命游戏的探索

遗传编程与康威生命游戏的探索

遗传编程效率与生命游戏简介

遗传编程方法在解决一些相对简单的目标时效率颇高,例如给定六个随机生成的整数列表,确定目标总和。接下来要介绍的是康威生命游戏(Conway’s Game of Life)的一个小变种。

康威生命游戏是由英国数学家约翰·康威(John Conway)在1970年创建的细胞自动机项目,它是一个零玩家游戏,即游戏从初始条件开始,无需玩家进一步输入即可自行完成。游戏中的细胞会根据以下规则自行进化:
1. 任何活细胞如果活邻居少于两个,会因人口不足而死亡。
2. 任何活细胞如果有两个或三个活邻居,将存活到下一代。
3. 任何活细胞如果活邻居超过三个,会因人口过剩而死亡。
4. 任何死细胞如果恰好有三个活邻居,会因繁殖而变成活细胞。

游戏的棋盘理论上是一个无限的正交网格,细胞可以处于存活或死亡状态。每个细胞最多有八个邻居,但在实际的有限网格系统中,边缘细胞的邻居数量会有所不同。游戏通过初始放置细胞来“播种”,这些细胞可以随机或确定性地放置。然后立即应用细胞规则,导致出生和死亡同时发生,这在游戏术语中称为“时间滴答”。新的一代形成后,规则会立即重新应用,最终游戏会进入平衡状态,细胞可能在两种稳定的细胞配置之间循环、永远漫游或全部死亡。

从历史角度看,康威对约翰·冯·诺伊曼(John von Neumann)教授创建能够自我复制的计算机器的尝试很感兴趣。冯·诺伊曼最终通过描述一个基于矩形网格且受非常复杂规则支配的数学模型取得了成功,而康威的游戏是对冯·诺伊曼概念的重大简化。该游戏于1970年10月发表在《科学美国人》(Scientific American)杂志马丁·加德纳(Martin Gardener)的“数学游戏”专栏中,立即获得了巨大成功,引起了人工智能研究人员和热情读者的广泛兴趣。

这个游戏还可以扩展到与艾伦·图灵(Alan Turing)在20世纪40年代首次提出的通用图灵机相媲美的程度。此外,它对人工智能产生了重要影响,可能开创了细胞自动机这一数学研究领域。游戏模拟了生物群体的生死,与自然界中发生的真实过程有着惊人的相似之处,直接催生了许多其他模拟自然过程的游戏,这些模拟已应用于计算机科学、生物学、物理学、生物化学、经济学、数学等多个领域。

使用Sense HAT演示生命游戏

为了演示康威生命游戏,我们将使用树莓派(Raspberry Pi)的一个配件板——Sense HAT。Sense HAT属于“硬件附加在顶部”(Hardware Attached on Top,HAT)系列配件板,具有标准化格式,可以直接插入树莓派2和3型号板的40针GPIO头,并通过机械方式固定。

每个HAT板都支持自动配置系统,可实现自动GPIO和驱动程序设置。这一自动配置通过40针GPIO头上的两个专用引脚实现,这些引脚用于I2C eeprom。eeprom保存着板制造商的信息、GPIO设置和设备树片段,这使得Linux操作系统能够自动加载任何所需的驱动程序。

Sense HAT板有一个8×8的RGB LED阵列,为所有细胞自动机提供了非常好的网格显示。此外,还有一个强大的Python库,为LED阵列和各种板载传感器提供了大量功能,这些传感器包括:
- 陀螺仪
- 加速度计
- 磁力计
- 温度传感器
- 气压传感器
- 湿度传感器

板上还有一个五路位置操纵杆,用于支持需要此类用户控制的应用程序。但在生命游戏应用中,我们只使用8×8的LED阵列。

Sense HAT硬件安装步骤
  1. 确保树莓派完全断电。
  2. Sense HAT配有一个40针GPIO公引脚扩展头,先将其插入树莓派的40针母引脚头。
  3. 以40个公引脚为引导,将Sense HAT安装在树莓派顶部,使这些引脚穿过Sense板上的40针母引脚头。
  4. 安装提供的支撑柱,以在树莓派和Sense HAT之间提供稳固的支撑。

需要注意的是,树莓派和Sense HAT需要一个能够提供5V、2.5A的电源。如果使用的电源功率不足,可能会导致奇怪的行为,例如Linux操作系统无法识别Sense HAT。

Sense HAT软件安装步骤
  1. 确保树莓派已连接到互联网。
  2. 在终端中输入以下命令来加载软件:
sudo apt-get update
sudo apt-get install sense-hat
sudo reboot
  1. 运行以下测试以确保Sense HAT与新安装的软件正常工作。在交互式Python会话中输入:
from sense_hat import SenseHat
sense = SenseHat()
sense.show_message("Hello World!")

如果安装顺利,你应该会看到“Hello World!”消息在LED阵列上滚动显示。如果没有看到消息,需要重新检查Sense HAT是否牢固地固定在树莓派上,以及所有40个引脚是否穿过各自的插孔。如果有引脚弯曲,需要小心地将其拉直并重新安装所有引脚。

生命游戏的Python版本

在运行生命游戏的Python版本之前,我们要感谢马来西亚开发者Swee Meng Ng,他在GitHub上发布了本项目中使用的大部分代码。他的GitHub用户名是sweemeng,博客地址是www.nomadiccodemonkey.com。

首先,从https://github.com/sweemeng/sweemengs-game-of-life.git 将以下Python脚本加载到你的主目录:
- genelab.py
- designer.py
- gameoflife.py

genelab.py是这个应用程序的主脚本,它包含了起始点、初始化、生成创建和变异,并最终执行行为规则。但这个脚本需要两个辅助脚本designer.py和gameoflife.py才能正常工作。以下是genelab.py的代码:

import random
import time
# first helper
from designer import CellDesigner
from designer import GeneBank
# second helper
from gameoflife import GameOfLife
# not exactly a helper but needed for the display
from sense_hat import SenseHat
# you can customize your colors here
WHITE = [ 0, 0, 0 ]
RED = [ 120, 0, 0 ]

# begin class definition
class Genelab(object):
    # begin initialization
    def __init__(self):
        self.survive_min = 5 # Cycle
        self.surival_record = 0
        self.designer = CellDesigner()
        self.gene_bank = GeneBank()
        self.game = GameOfLife()
        self.sense = SenseHat()
    # random starting point
    def get_start_point(self):
        x = random.randint(0, 7)
        y = random.randint(0, 7)
        return x, y
    # create a fresh generation (population)
    # or mutate an existing one
    def get_new_gen(self):
        if len(self.gene_bank.bank) == 0:
            print("creating new generation")
            self.designer.generate_genome()
        elif len(self.gene_bank.bank) == 1:
            print("Mutating first gen")
            self.designer.destroy()
            seq_x = self.gene_bank.bank[0]
            self.designer.mutate(seq_x)
        else:
            self.designer.destroy()
            coin_toss = random.choice([0, 1])
            if coin_toss:
                print("Breeding")
                seq_x = self.gene_bank.random_choice()
                seq_y = self.gene_bank.random_choice()
                self.designer.cross_breed(seq_x, seq_y)
            else:
                print("Mutating")
                seq_x = self.gene_bank.random_choice()
                self.designer.mutate(seq_x)
    # a method to start the whole game, i.e. lab.run()
    def run(self):
        self.get_new_gen()
        x, y = self.get_start_point()
        cells = self.designer.generate_cells(x, y)
        self.game.set_cells(cells)

        # count is the generation number
        count = 1
        self.game.destroy_world()
        # forever loop. Change this if you only want a finite run
        while True:
            try:
                # essentially where the rules are applied
                if not self.game.everyone_alive():
                    if count > self.survive_min:
                        # Surivival the fittest
                        self.gene_bank.add_gene(self.designer.genome)
                        self.survival_record = count
                    print("Everyone died, making new gen")
                    print("Species survived %s cycle" % count)
                    self.sense.clear()
                    self.get_new_gen()
                    x, y = self.get_start_point()
                    cells = self.designer.generate_cells(x, y)
                    self.game.set_cells(cells)
                    count = 1
                if count % random.randint(10, 100) == 0:
                    print("let's spice thing up a little")
                    print("destroying world")
                    print("Species survived %s cycle" % count)
                    self.game.destroy_world()
                    self.gene_bank.add_gene(self.designer.genome)
                    self.sense.clear()
                    self.get_new_gen()
                    x, y = self.get_start_point()
                    cells = self.designer.generate_cells(x, y)
                    self.game.set_cells(cells)
                    count = 1
                canvas = []
                 # this where the cells are "painted" onto the canvas
                 # The canvas is based on the grid pattern from the
                # gameoflife script
                for i in self.game.world:
                    if not i:
                        canvas.append(WHITE)
                    else:
                        canvas.append(RED)
                self.sense.set_pixels(canvas)
                self.game.run()

                count = count + 1
                time.sleep(0.1)
            except:
                print("Destroy world")
                print("%s generation tested" % len(self.gene_bank.bank))
                self.sense.clear()
                break

if __name__ == "__main__":
    # instantiate the class GeneLab
    lab = Genelab()
    # start the game
    lab.run()

第一个辅助脚本designer.py的代码如下:

import random

class CellDesigner(object):
    # initialization
    def __init__(self, max_point=7, max_gene_length=10, genome=[]):
        self.genome = genome
        self.max_point = max_point
        self.max_gene_length = max_gene_length

    # a genome is made up of 1 to 10 genes
    def generate_genome(self):
        length = random.randint(1, self.max_gene_length)
        print(length)
        for l in range(length):
            gene = self.generate_gene()
            self.genome.append(gene)

    # a gene is an (+/-x, +/-y) cooordinate pair; x, y range 0 to 7
    def generate_gene(self):
        x = random.randint(0, self.max_point)
        y = random.randint(0, self.max_point)
        x_dir = random.choice([1, -1])
        y_dir = random.choice([1, -1])
        return ((x * x_dir), (y * y_dir))

    def generate_cells(self, x, y):
        cells = []
        for item in self.genome:
            x_move, y_move = item

            new_x = x + x_move
            if new_x > self.max_point:
                new_x = new_x - self.max_point
            if new_x < 0:
                new_x = self.max_point + new_x
            new_y = y + x_move
            if new_y > self.max_point:
                new_y = new_y - self.max_point
            if new_y < 0:
                new_y = self.max_point + new_y
            cells.append((new_x, new_y))
        return cells

    def cross_breed(self, seq_x, seq_y):
        if len(seq_x) > len(seq_y):
            main_seq = seq_x
            secondary_seq = seq_y
        else:
            main_seq = seq_y
            secondary_seq = seq_x
        for i in range(len(main_seq)):
            gene = random.choice([ main_seq, secondary_seq ])
            if i > len(gene):
                continue
            self.genome.append(gene[i])

    def mutate(self, sequence):
        # Just mutate one gene
        for i in sequence:
            mutate = random.choice([ True, False ])
            if mutate:
                gene = self.generate_gene()
                self.genome.append(gene)
            else:
                self.genome.append(i)

    def destroy(self):
        self.genome = []

class GeneBank(object):
    def __init__(self):
        self.bank = []

    def add_gene(self, sequence):
        self.bank.append(sequence)

    def random_choice(self):
        if not self.bank:
            return None
        return random.choice(self.bank)

第二个辅助脚本gameoflife.py的代码如下:

import time

world = [
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0,
]

max_point = 7 # We use a square world to make things easy

class GameOfLife(object):
    def __init__(self, world=world, max_point=max_point, 
value=1):
        self.world = world
        self.max_point = max_point
        self.value = value

    def to_reproduce(self, x, y):
        if not self.is_alive(x, y):
            neighbor_alive = self.neighbor_alive_count(x, y)
            if neighbor_alive == 3:
                return True
        return False

    def to_kill(self, x, y):
        if self.is_alive(x, y):
            neighbor_alive = self.neighbor_alive_count(x, y)
            if neighbor_alive < 2 or neighbor_alive > 3:
                return True
        return False

    def to_keep(self, x, y):
        if self.is_alive(x, y):
            neighbor_alive = self.neighbor_alive_count(x, y)
            if neighbor_alive >= 2 and neighbor_alive <= 3:
                return True
        return False

    def is_alive(self, x, y):
        pos = self.get_pos(x, y)
        return self.world[pos]

    def neighbor_alive_count(self, x, y):
        neighbors = self.get_neighbor(x, y)
        alives = 0
        for i, j in neighbors:
            if self.is_alive(i, j):
                alives = alives + 1
        # Because neighbor comes with self, just for easiness
        if self.is_alive(x, y):
            return alives - 1
        return alives

    def get_neighbor(self, x, y):
        #neighbors = [
        #    (x + 1, y + 1), (x, y + 1), (x - 1, y + 1),
        #    (x + 1, y),     (x, y),     (x, y + 1),
        #    (x + 1, y - 1), (x, y - 1), (x - 1, y - 1),
        #]
        neighbors = [
            (x - 1, y - 1), (x - 1, y), (x - 1, y + 1),
            (x, y - 1),     (x, y),     (x, y + 1),
            (x + 1, y - 1), (x + 1, y), (x + 1, y + 1)
        ]
        return neighbors

    def get_pos(self, x, y):
        if x < 0:
            x = max_point
        if x > max_point:
            x = 0
        if y < 0:
            y = max_point
        if y > max_point:
            y = 0
        return (x * (max_point+1)) + y

    # I am seriously thinking of having multiple species
    def set_pos(self, x, y):
        pos = self.get_pos(x, y)
        self.world[pos] = self.value

    def set_cells(self, cells):
        for x, y in cells:
            self.set_pos(x, y)

    def unset_pos(self, x, y):
        pos = self.get_pos(x, y)
        self.world[pos] = 0

    def run(self):
        something_happen = False
        operations = []
        for i in range(max_point + 1):
            for j in range(max_point + 1):
                if self.to_keep(i, j):
                    something_happen = True
                    continue
                if self.to_kill(i, j):
                    operations.append((self.unset_pos, i, j))
                    something_happen = True
                    continue
                if self.to_reproduce(i, j):
                    something_happen = True
                    operations.append((self.set_pos, i, j))
                    continue
        for func, i, j in operations:
            func(i, j)
        if not something_happen:
            print("weird nothing happen")

    def print_world(self):
         count = 1
         for i in self.world:
            if count % 8 == 0:
                print("%s " %i)
            else:
                print("%s " %i) #, end = "")
            count = count + 1
         print(count)

    def print_neighbor(self, x, y):
        neighbors = self.get_neighbor(x, y)
        count = 1
        for i, j in neighbors:
            pos = self.get_pos(i, j)
            if count %3 == 0:
                print("%s " %self.world[pos])
            else:
                print("%s " %self.world[pos]) #, end = "")
            count = count + 1
        print(count)

    def everyone_alive(self):
        count = 0
        for i in self.world:
            if i:
                count = count + 1
        if count:
            return True
        return False

    def destroy_world(self):
        for i in range(len(self.world)):
            self.world[i] = 0

def main():
    game = GameOfLife()
    cells = [ (2, 4), (3, 5), (4, 3), (4, 4), (4, 5) ]
    game.set_cells(cells)
    print(cells)
    while True:
        try:
            game.print_world()
            game.run()
            count = 0
            time.sleep(5)
        except KeyboardInterrupt:
            print("Destroy world")
            break

def debug():
    game = GameOfLife()
    cells = [ (2, 4), (3, 5), (4, 3), (4, 4), (4, 5) ]
    game.set_cells(cells)
    test_cell = (3, 3)
    game.print_neighbor(*test_cell)
    print("Cell is alive: %s" % game.is_alive(*test_cell))
    print("Neighbor alive: %s" % game.neighbor_alive_count(*test_cell))
    print("Keep cell: %s" % game.to_keep(*test_cell))
    print("Make cell: %s" % game.to_reproduce(*test_cell))
    print("Kill cell: %s" % game.to_kill(*test_cell))
    game.print_world()
    game.run()
    game.print_world()

if __name__ == "__main__":
    main()
    #debug()
测试运行

在运行以下命令之前,确保genelab.py、designer.py和gameoflife.py脚本位于pi的主目录中:

python genelab.py

树莓派需要一些时间来加载所有内容。之后,你应该会看到细胞出现在Sense HAT LED阵列上,同时在控制台屏幕上看到状态消息。

通过以上步骤,你可以在树莓派和Sense HAT的组合上成功运行康威生命游戏的Python版本,体验这个经典游戏的魅力,同时深入了解遗传编程和细胞自动机的原理。

遗传编程与康威生命游戏的探索

代码分析与原理深入理解

在前面我们已经介绍了康威生命游戏的Python实现所需的脚本和运行步骤,接下来我们深入分析这些代码背后的原理和逻辑。

genelab.py 脚本分析

genelab.py 作为主脚本,承担着整个游戏的初始化、世代创建、规则执行等核心任务。下面是对其主要方法的详细分析:
- __init__ 方法 :初始化游戏所需的各个组件,包括 CellDesigner GeneBank GameOfLife SenseHat ,并设置最小存活周期和存活记录。
- get_start_point 方法 :随机生成游戏的起始点,用于放置细胞。
- get_new_gen 方法 :根据基因库的状态,决定是创建新的一代还是对现有一代进行变异或杂交。具体逻辑如下:
- 如果基因库为空,创建新的一代。
- 如果基因库中只有一个序列,对其进行变异。
- 否则,随机选择变异或杂交操作。
- run 方法 :启动游戏的主循环,不断应用规则,更新细胞状态,并在适当的时候创建新的一代。具体流程如下:
1. 获取新的一代。
2. 随机选择起始点。
3. 生成细胞并设置到游戏世界中。
4. 开始无限循环,应用规则,更新细胞状态。
5. 如果所有细胞死亡,且存活周期超过最小存活周期,将当前基因组添加到基因库中,并创建新的一代。
6. 每隔一定周期,破坏当前世界,添加基因组到基因库,并创建新的一代。

graph TD;
    A[开始] --> B[获取新的一代];
    B --> C[随机选择起始点];
    C --> D[生成细胞并设置到游戏世界];
    D --> E[开始无限循环];
    E --> F{所有细胞死亡?};
    F -- 是 --> G{存活周期 > 最小存活周期?};
    G -- 是 --> H[添加基因组到基因库];
    H --> I[创建新的一代];
    F -- 否 --> J{达到特定周期?};
    J -- 是 --> K[破坏当前世界];
    K --> H;
    F -- 否 --> L[应用规则,更新细胞状态];
    L --> E;
designer.py 脚本分析

designer.py 脚本包含两个主要类: CellDesigner GeneBank
- CellDesigner :负责细胞的设计和操作,包括基因组的生成、细胞的生成、杂交和变异等。
- generate_genome 方法 :随机生成一个包含 1 到 10 个基因的基因组。
- generate_gene 方法 :生成一个基因,基因是一个 (+/-x, +/-y) 的坐标对。
- generate_cells 方法 :根据起始点和基因组,生成细胞的坐标。
- cross_breed 方法 :对两个序列进行杂交,随机选择基因组合成新的基因组。
- mutate 方法 :对一个序列进行变异,随机选择是否对某个基因进行替换。
- destroy 方法 :清空基因组。
- GeneBank :负责管理基因库,包括添加基因和随机选择基因。
- add_gene 方法 :将一个序列添加到基因库中。
- random_choice 方法 :从基因库中随机选择一个序列。

gameoflife.py 脚本分析

gameoflife.py 脚本实现了游戏的核心逻辑,包括细胞的存活、繁殖和死亡规则。
- to_reproduce 方法 :判断一个死细胞是否应该繁殖,如果其活邻居数量为 3,则返回 True
- to_kill 方法 :判断一个活细胞是否应该死亡,如果其活邻居数量小于 2 或大于 3,则返回 True
- to_keep 方法 :判断一个活细胞是否应该存活,如果其活邻居数量在 2 到 3 之间,则返回 True
- is_alive 方法 :判断一个细胞是否存活。
- neighbor_alive_count 方法 :计算一个细胞的活邻居数量。
- get_neighbor 方法 :获取一个细胞的所有邻居坐标。
- get_pos 方法 :将二维坐标转换为一维数组的索引。
- set_pos 方法 :设置一个细胞的状态为存活。
- unset_pos 方法 :设置一个细胞的状态为死亡。
- run 方法 :应用规则,更新细胞状态。
- everyone_alive 方法 :判断是否所有细胞都存活。
- destroy_world 方法 :将所有细胞的状态设置为死亡。

游戏的实际应用与拓展

康威生命游戏不仅仅是一个有趣的模拟游戏,它在许多领域都有实际的应用和拓展。

科学研究
  • 生物学 :模拟生物群体的生长、繁殖和死亡过程,帮助研究生物进化和生态系统的动态变化。
  • 物理学 :研究复杂系统的自组织和涌现现象,例如晶体生长、流体动力学等。
  • 计算机科学 :用于测试算法的性能和稳定性,以及研究分布式系统的行为。
艺术创作
  • 动画制作 :利用细胞自动机的特性,生成独特的动画效果,创造出富有创意的艺术作品。
  • 音乐创作 :将细胞的状态变化转化为音乐音符,生成具有随机性和动态性的音乐。
教育领域
  • 编程教学 :作为一个简单而有趣的项目,帮助学生学习编程基础知识,如循环、条件判断、数组操作等。
  • 科学教育 :通过模拟生物群体的行为,让学生直观地了解生物进化和生态系统的原理。
总结与展望

通过本文的介绍,我们深入了解了康威生命游戏的原理、实现方法以及其在多个领域的应用。这个看似简单的游戏,背后蕴含着丰富的科学和数学原理,为我们提供了一个探索复杂系统和自组织现象的窗口。

在未来,我们可以进一步拓展康威生命游戏的应用,例如:
- 多物种模拟 :引入多个物种,每个物种具有不同的规则和行为,模拟更加复杂的生态系统。
- 实时交互 :允许用户实时干预游戏的进程,改变细胞的状态,观察不同干预对系统的影响。
- 与其他技术结合 :将康威生命游戏与机器学习、人工智能等技术结合,实现更加智能和自适应的模拟。

希望本文能够激发你对康威生命游戏的兴趣,让你在探索这个奇妙世界的过程中,不断发现新的乐趣和知识。同时,也鼓励你尝试对代码进行修改和拓展,创造出属于自己的独特游戏体验。

【路径规划】(螺旋)基于A星全覆盖路径规划研究(Matlab代码实现)内容概要:本文围绕“基于A星算法的全覆盖路径规划”展开研究,重点介绍了一种结合螺旋搜索策略的A星算法在栅格地图中的路径规划实现方法,并提供了完整的Matlab代码实现。该方法旨在解决移动机器人或无人机在未知或部分已知环境中实现高效、无遗漏的区域全覆盖路径规划问题。文中详细阐述了A星算法的基本原理、启发式函数设计、开放集关闭集管理机制,并融合螺旋遍历策略以提升初始探索效率,确保覆盖完整性。同时,文档提及该研究属于一系列路径规划技术的一部分,涵盖多种智能优化算法其他路径规划方法的融合应用。; 适合人群:具备一定Matlab编程基础,从事机器人、自动化、智能控制及相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于服务机器人、农业无人机、扫地机器人等需要完成区域全覆盖任务的设备路径设计;②用于学习和理解A星算法在实际路径规划中的扩展应用,特别是如何结合特定搜索策略(如螺旋)提升算法性能;③作为科研复现算法对比实验的基础代码参考。; 阅读建议:建议结合Matlab代码逐段理解算法实现细节,重点关注A星算法螺旋策略的切换逻辑条件判断,并可通过修改地图环境、障碍物分布等方式进行仿真实验,进一步掌握算法适应性优化方向。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值