The Nature of Code閱讀心得與Python實作:7.6 Variations on...

更新於 發佈於 閱讀時間約 42 分鐘
這一節的標題是
Variations on Traditional CA
因為方格子標題字數限制,所以沒完整顯現

CA的細胞長相是不是一定就是方形的呢?當然不是!它可以有各式各樣的形狀,甚至於也可以有各種不同的性質及演化方式;唯一的限制,就是你的想像力。這一節就來看看,利用先前所寫的CA程式,還可以玩出什麼不同的花樣出來。

Nonrectangular Grids

CA細胞的形狀不是非得方形不可,排列的方式也不是非得像棋盤狀不可。

Exercise 7.8

raw-image

要畫正六邊形,可以先找出六個頂點,然後用pygame的draw.polygon()方法來畫。假設中心點位於(0, 0),因為正六邊形是由六個正三角形拼排而成,頂點的位置很容易就能從圖形直接算出來。寫程式時,利用極座標轉直角坐標的方式來寫,會比較省時省力。

在細胞的排列方面,由圖可以看出來,直行間隔為1.5w,而橫列間隔則為2h。不過要注意的是,相鄰兩直行的細胞,其中心點的位置在y方向偏移了h的距離。

完整的程式及執行結果如下:

raw-image
class Cell:
def __init__(self, state, x, y, cell_size):
self.screen = pygame.display.get_surface()

# 當前狀態
self.state = state
# 前一世代時的狀態
self.previous = state

self.x = x
self.y = y
self.cell_size = cell_size

def show(self):
center = pygame.Vector2(self.x, self.y)
vertices = []
for theta in range(0, 360, 60):
vertex = center + pygame.Vector2.from_polar((self.cell_size, theta))
vertices.append(vertex)

pygame.draw.polygon(self.screen, (0, 0, 0), vertices, 1-self.state)


# python version 3.10.9
import math
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 7.8")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 240
screen = pygame.display.set_mode(screen_size)

FPS = 1
frame_rate = pygame.time.Clock()

# 細胞尺寸
cell_size = 20
h = cell_size*math.cos(math.pi/6)

columns, rows = int(WIDTH/(1.5*cell_size))+1, int(HEIGHT/(2*h))+1

board = [[0]*columns for _ in range(rows)]
for j in range(columns):
x = 1.5*cell_size*j
for i in range(rows):
y = (2*i+1-j%2)*h
board[i][j] = Cell(0, x, y, cell_size)

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

screen.fill(WHITE)

# 畫出細胞
for i in range(rows):
for j in range(columns):
board[i][j].state = random.randint(0, 1)
board[i][j].show()

pygame.display.update()
frame_rate.tick(FPS)

Probabilistic

CA的迭代規則也可以摻雜有機率的成分。

Exercise 7.9

以物件導向的方式來寫,加入新規則後之程式碼為:

# 依據規則決定細胞的新狀態
if board[i][j].previous == 1 and neighbor_sum >= 4:
# 規則 1;過度擁擠,有80%的機率死亡
board[i][j].state = 0 if random.random() < 0.8 else 1
elif board[i][j].previous == 1 and neighbor_sum <= 1:
# 規則 1;孤單,有60%的機率死亡
board[i][j].state = 0 if random.random() < 0.6 else 1
elif board[i][j].previous == 0 and neighbor_sum == 3:
# 規則 2
board[i][j].state = 1
else:
# 規則 3
board[i][j].state = board[i][j].previous

Continuous

細胞的狀態值並不是非要整數不可,也可以是浮點數。

Exercise 7.10

修改Exercise 7.1程式,將規則集設定成0~1間的亂數,並修改rules()函數中關於索引值的計算方式。另外,畫細胞時,只畫出狀態值>=0.5的細胞。

raw-image
def rules(left, middle, right, ruleset):
idx = int(4*left + 2*middle + right) % 8
return ruleset[7-idx]


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 7.10")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 240
screen = pygame.display.set_mode(screen_size)

FPS = 1
frame_rate = pygame.time.Clock()

# 細胞方塊邊長
cell_size = 10

# 細胞數量
n_cells = WIDTH // cell_size

# 世代數量
n_generation = HEIGHT // cell_size

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

screen.fill(WHITE)

# 規則
ruleset = [random.random() for _ in range(8)]

# 初代CA
cells = [0]*n_cells
cells[n_cells//2] = 0.5

next_generation = cells.copy()

for generation in range(n_generation):
for i in range(n_cells):
# 只畫出狀態值>=0.5的細胞
if cells[i] >= 0.5:
rect = pygame.Rect(i*cell_size, generation*cell_size, cell_size, cell_size)
pygame.draw.rect(screen, (0, 0, 0), rect)

for i in range(1, n_cells-1):
left = cells[i-1]
middle = cells[i]
right = cells[i+1]
next_generation[i] = rules(left, middle, right, ruleset)

cells = next_generation.copy()

pygame.display.update()
frame_rate.tick(FPS)

Image Processing

有許多影像處理(image processing)演算法的做法,其實和CA的做法很類似;例如,要讓影像變模糊時,就是把像素的顏色,設定成周遭像素顏色的平均值。

Exercise 7.11

設定細胞的大小為1個像素,而其狀態值是(r, g, b),也就是一個包含三個元素的tuple。其中rgb都是介於0~255的整數。

raw-image
def rules(left, middle, right, ruleset):
r = (left[0] + middle[0] + right[0]) % 256
g = (left[1] + middle[1] + right[1]) % 256
b = (left[2] + middle[2] + right[2]) % 256
return (ruleset[r], ruleset[g], ruleset[b])


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 7.11")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 240
screen = pygame.display.set_mode(screen_size)

FPS = 1
frame_rate = pygame.time.Clock()

# 細胞數量
n_cells = WIDTH

# 世代數量
n_generation = HEIGHT

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

screen.fill(WHITE)

# 隨機選取的規則集
ruleset = [random.randint(0, 255) for _ in range(256)]

# 初代CA
cells = [(0, 0, 0)]*n_cells
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cells[random.randint(0, n_cells)] = color

next_generation = cells.copy()

for generation in range(n_generation):
# 畫出細胞
for i in range(n_cells):
screen.set_at((i, generation), cells[i])

for i in range(1, n_cells-1):
left, middle, right = cells[i-1], cells[i], cells[i+1]
next_generation[i] = rules(left, middle, right, ruleset)

cells = next_generation.copy()

pygame.display.update()
frame_rate.tick(FPS)

Historical

在用物件導向的寫法寫「生命遊戲」時,是用兩個變數來記錄細胞在當前及上一世代時的狀態值。那如果我們用一個list來記錄、追蹤細胞在很長一段期間內的狀態值,不就能讓細胞可以根據過往的生命歷程,隨著時間的流逝,不斷地調整它的活動規則嗎?這個就是後續會再詳細介紹的複雜適應性系統(complex adaptive system)的概念。

Exercise 7.12

利用一個list來記錄細胞在最近100個世代中的狀態,並依據記錄中細胞曾經活著的世代總數來設定細胞的顏色。另外,如果細胞在有記錄的所有世代中,都持續維持相同的狀態,則會有50%的機會改變其狀態。

raw-image
class Cell:
def __init__(self, state, x, y, cell_size):
self.screen = pygame.display.get_surface()

# 當前狀態
self.state = state

# 狀態歷程
self.history = [state]

# 細胞位置
self.x, self.y = x, y

self.cell_size = cell_size

def remember_state(self):
self.history.append(self.state)
# 只記錄100世代內的狀態
if len(self.history) > 100:
del self.history[0]

def show(self):
# 依據記錄中曾經活著的世代總數來設定細胞的顏色
n = sum(self.history)
match n:
case n if n < 25:
color = (0, 0, 0)
case n if 25 <= n < 50:
color = (255, 0, 0)
case n if 50 <= n < 75:
color = (0, 255, 0)
case _:
color = (0, 0, 255)

rect = pygame.Rect(self.x, self.y, self.cell_size, self.cell_size)
if self.state == 0:
pygame.draw.rect(screen, color, rect, 1)
else:
pygame.draw.rect(screen, color, rect)


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 7.12")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 640, 240
screen = pygame.display.set_mode(screen_size)

FPS = 10
frame_rate = pygame.time.Clock()

# 細胞方塊邊長
cell_size = 10

columns, rows = WIDTH//cell_size, HEIGHT//cell_size

board = [[Cell(0, j*cell_size, i*cell_size, cell_size) for j in range(columns)] for i in range(rows)]

# 設定初始樣式
for i in range(1, rows-1):
for j in range(1, columns-1):
board[i][j].state = random.randint(0, 1)
board[i][j].remember_state()

screen.fill(WHITE)

# 畫出初始樣式
for i in range(rows):
for j in range(columns):
board[i][j].show()

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

screen.fill(WHITE)

# 計算細胞新的狀態;排除位於邊緣的細胞
for i in range(1, rows-1):
for j in range(1, columns-1):
# 計算活著的鄰居數量
neighbor_sum = sum(cell.history[-1] for cell in board[i-1][j-1:j+2]) + \
sum(cell.history[-1] for cell in board[i][j-1:j+2:2]) + \
sum(cell.history[-1] for cell in board[i+1][j-1:j+2])

# 依據規則決定細胞的新狀態
if board[i][j].history[-1] == 1 and (neighbor_sum >= 4 or neighbor_sum <= 1):
# 規則 1
board[i][j].state = 0
elif board[i][j].history[-1] == 0 and neighbor_sum == 3:
# 規則 2
board[i][j].state = 1
else:
# 規則 3
board[i][j].state = board[i][j].history[-1]

# 如果細胞在歷史記錄中從未改變狀態,則有50%的機會改變狀態
n = sum(board[i][j].history)
if (n in [0, len(board[i][j].history)]) and (random.random() < 0.5):
board[i][j].state = 1 if n==0 else 0

# 畫出細胞並記錄其狀態
for i in range(rows):
for j in range(columns):
board[i][j].show()
board[i][j].remember_state()

pygame.display.update()
frame_rate.tick(FPS)

Moving Cells

細胞不一定非得待在同樣的位置上不可,它也可以在畫面上到處亂跑。

Exercise 7.13

修改Example 5.12。在Boid類別的__init__()方法中加入

self.state = random.random()

來使boid具有state屬性,並設定起始值為0~1間的亂數。

修改flock()方法,讓狀態值隨周遭同伴的數量是否適中而升、降,並讓狀態值成為影響boid行動能力的因素。

def flock(self, boids):
# 周遭同伴數量適中,則狀態值升高,行動能力提升,
# 否則狀態值降低,行動能力變弱
n = len(boids)
if 50 <= n <= 100:
self.state += 0.001
else:
self.state -= 0.001

# 狀態值0,移動速度會變慢
if self.state < 0:
self.state = 0
self.velocity *= 0.9

separation = self.separate(boids)
alignment = self.align(boids)
coherence = self.cohere(boids)

# 狀態值會強化或減弱轉向力
steer = self.state*(1.5*separation + alignment + coherence)
self.apply_force(steer)

Nesting

複雜系統的一個特徵是,它可以層層套疊而形成一個更大的複雜系統。例如,城市是由人所組成的複雜系統,人是由器官所組成的複雜系統,而器官則是由細胞所組成的複雜系統。事實上,這種層層套疊的方式,也是我們在研究、處理許多事物時,會採用的方式。例如在第四章中,我們讓許多粒子組成一個粒子系統,然後再讓許多粒子系統組成一個更大的粒子系統;如果有需要,可以就這麼繼續下去。

上述這種層層套疊的方式,也可以應用到CA上嗎?當然可以!我們可以把好幾個CA所組成的系統,看作是一個更大CA系統的細胞;這種方式可以一直持續下去,直到你滿意為止。

Exercise 7.14

讓「生命遊戲」的細胞由基礎CA所構成。當基礎CA的細胞有超過半數以上活著時,「生命遊戲」的細胞會變成紅色。另外,當「生命遊戲」的細胞活著時,基礎CA細胞的狀態值會隨機產生,而不是依照規則集產生。

raw-image
class Cell:
def __init__(self, state, x, y, cell_size):
self.screen = pygame.display.get_surface()

# 當前與前一世代時的狀態
self.state = state
self.previous = state

# 細胞位置與尺寸
self.x, self.y = x, y
self.cell_size = cell_size

def show(self):
rect = pygame.Rect(self.x, self.y, self.cell_size, self.cell_size)
if self.previous == 0 and self.state == 1:
# 活過來的細胞塗上藍色
pygame.draw.rect(screen, (0, 0, 255), rect)
elif self.previous == 1 and self.state == 0:
# 由生轉死的細胞塗上紅色
pygame.draw.rect(screen, (255, 0, 0), rect)
elif self.state == 1:
pygame.draw.rect(screen, (0, 0, 0), rect)
else:
pygame.draw.rect(screen, (0, 0, 0), rect, 1)


class ElementaryCA():
def __init__(self, state, x, y, size, rule_number, length):
self.screen = pygame.display.get_surface()

# 當前與前一世代時的狀態
self.state = state
self.previous = self.state

# 位置與尺寸
self.x, self.y = x, y
self.size = size

# 基礎CA所含細胞數量
self.length = length

# 細胞迭代規則集
self.ruleset = [int(c) for c in f'{rule_number:08b}']

# 細胞初始狀態
cells_state = [int(c) for c in f'{self.state:0{self.length}b}']
self.cells = [Cell(cells_state[i], i, 1, 1) for i in range(self.length)]

def calculate_state(self):
# 計算細胞狀態。當基礎CA的狀態值為1時,其細胞的狀態值為隨機產生。
for i in range(self.length):
left = self.cells[(i-1)%self.length].previous
middle = self.cells[i].previous
right = self.cells[(i+1)%self.length].previous
idx = 4*left + 2*middle + right
self.cells[i].state = self.ruleset[7-idx] if self.state==0 else random.randint(0, 1)

# 記住細胞當前的狀態
for i in range(self.length):
self.cells[i].previous = self.cells[i].state

def show(self):
# 如果有超過半數以上的細胞活著,則基礎CA為紅色;否則為黑色
color = (255, 0, 0) if sum(cell.state for cell in self.cells)>self.length/2 else (0, 0, 0)
rect = pygame.Rect(self.x, self.y, self.size, self.size)
pygame.draw.rect(screen, color, rect, 1-self.state)


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Exercise 7.14")

WHITE = (255, 255, 255)

screen_size = WIDTH, HEIGHT = 750, 360
screen = pygame.display.set_mode(screen_size)

FPS = 10
frame_rate = pygame.time.Clock()

# 基礎CA方塊邊長
ECA_size = 20

# 基礎CA規則集
rule_number = 90

# 基礎CA細胞數量
length = 3

# 側邊欄寬度
sidebar = 150

board_width = WIDTH - sidebar

columns, rows = board_width//ECA_size, HEIGHT//ECA_size

board = [[ElementaryCA(0, j*ECA_size, i*ECA_size, ECA_size, rule_number, length)
for j in range(columns)] for i in range(rows)]

screen.fill(WHITE)

# 狀態欄及按鈕文字
font = pygame.font.SysFont(None, 32)
text_status = font.render('', True, (0, 0, 0))
text_start = font.render('Start', True, (255, 255, 255), (0, 0, 0))
text_pause = font.render('Pause', True, (255, 255, 255), (0, 0, 0))
text_reset = font.render('Reset', True, (255, 255, 255), (0, 0, 0))

# 狀態欄及按鈕位置
status_block = text_status.get_rect()
status_block.topleft = (board_width+15, 50)

button_start = text_start.get_rect()
button_start.topleft = (board_width+35, 100)

button_pause = text_pause.get_rect()
button_pause.topleft = (board_width+35, 130)

button_reset = text_reset.get_rect()
button_reset.topleft = (board_width+35, 160)

evolving = False

while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
x, y = event.pos
if x <= board_width:
row, column = y//ECA_size, x//ECA_size
board[row][column].state = 1 - board[row][column].state
elif button_start.collidepoint(x, y):
evolving = True
text_status = font.render('Evolving...', True, (0, 0, 0))
elif button_pause.collidepoint(x, y):
evolving = False
text_status = font.render('Pausing...', True, (0, 0, 0))
elif button_reset.collidepoint(x, y):
evolving = False
text_status = font.render('', True, (0, 0, 0))
board = [[ElementaryCA(0, j*ECA_size, i*ECA_size, ECA_size, rule_number, length)
for j in range(columns)] for i in range(rows)]

if evolving:
# 計算細胞新的狀態
for i in range(rows):
for j in range(columns):
board[i][j].calculate_state()

for i in range(1, rows-1):
for j in range(1, columns-1):
# 計算活著的鄰居數量
neighbor_sum = -board[i][j].previous
for m in range(-1, 2):
row = (i+m) % rows
for n in range(-1, 2):
col = (j+n) % columns

neighbor_sum += board[row][col].previous

# 依據規則決定細胞的新狀態
if board[i][j].previous == 1 and (neighbor_sum >= 4 or neighbor_sum <= 1):
# 規則 1
board[i][j].state = 0
elif board[i][j].previous == 0 and neighbor_sum == 3:
# 規則 2
board[i][j].state = 1
else:
# 規則 3
board[i][j].state = board[i][j].previous

# 畫出ECA並記錄其狀態
for i in range(rows):
for j in range(columns):
board[i][j].show()
board[i][j].previous = board[i][j].state

# 放置狀態欄及按鈕
screen.blit(text_status, status_block.topleft)
screen.blit(text_start, button_start.topleft)
screen.blit(text_pause, button_pause.topleft)
screen.blit(text_reset, button_reset.topleft)

pygame.display.update()
frame_rate.tick(FPS)



留言
avatar-img
留言分享你的想法!
avatar-img
ysf的沙龍
19會員
154內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
2025/05/12
這一節要用物件導向的方式來寫「生命遊戲」。
Thumbnail
2025/05/12
這一節要用物件導向的方式來寫「生命遊戲」。
Thumbnail
2025/05/05
這一節介紹的是赫赫有名的2D CA:生命遊戲(the Game of Life)。
Thumbnail
2025/05/05
這一節介紹的是赫赫有名的2D CA:生命遊戲(the Game of Life)。
Thumbnail
2025/04/04
基礎CA的規則集總共有256個,經由這些規則集所生成的圖案,大部分看起來都平平無奇;不過,還是有些圖案真的是會讓人驚嘆不已,因為實在是跟自然界中可以看到的圖案樣式很相像。根據這些圖案的樣式和特性,Wolfram把它們分成四大類,接下來就來看看,這四大類的圖案各有怎樣的樣式和特性。
Thumbnail
2025/04/04
基礎CA的規則集總共有256個,經由這些規則集所生成的圖案,大部分看起來都平平無奇;不過,還是有些圖案真的是會讓人驚嘆不已,因為實在是跟自然界中可以看到的圖案樣式很相像。根據這些圖案的樣式和特性,Wolfram把它們分成四大類,接下來就來看看,這四大類的圖案各有怎樣的樣式和特性。
Thumbnail
看更多
你可能也想看
Thumbnail
高中數學主題練習—對數方程式
Thumbnail
高中數學主題練習—對數方程式
Thumbnail
高中數學主題練習—根式化簡
Thumbnail
高中數學主題練習—根式化簡
Thumbnail
高中數學主題練習—根式化簡
Thumbnail
高中數學主題練習—根式化簡
Thumbnail
解答來囉 (AI設計的縮圖代表算數學很耗能,沒錯!)
Thumbnail
解答來囉 (AI設計的縮圖代表算數學很耗能,沒錯!)
Thumbnail
在模擬自然界中的事物時導入隨機性,可以讓結果看起來比較自然,但如果導入的隨機性都是uniform distribution,那未免也太呆板了。這時候,我們需要nonuniform distribution亂數,來讓模擬出來的結果,更像真的一樣。
Thumbnail
在模擬自然界中的事物時導入隨機性,可以讓結果看起來比較自然,但如果導入的隨機性都是uniform distribution,那未免也太呆板了。這時候,我們需要nonuniform distribution亂數,來讓模擬出來的結果,更像真的一樣。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News