The Nature of Code閱讀心得與Python實作:7.5 Object-Oriented Cells

更新於 發佈於 閱讀時間約 14 分鐘

這一節要用物件導向的方式來寫「生命遊戲」。

既然先前沒有用物件導向的方式來寫都可以處理得很好,那為什麼要用物件導向的方式來寫呢?簡單來說,那是因為使用物件導向式的寫法,可以創造更多的可能性!

當細胞只有「狀態」這個屬性時,一個list的元素,就足以描述一個細胞的所有性質了;這也就是先前非物件導向式的寫法不會過於難寫的原因。可是,如果我們希望細胞除了「死」和「活」之外,也能夠有其他更複雜的行為或屬性,像是會到處亂跑、隨著環境而改變顏色,甚或本身就有一定長度的壽命,歷經一定數量的世代之後,就再也無法活過來等等,這些如果不用物件導向的方式來寫,那就真的是自找麻煩了。

要用物件導向的方式來寫「生命遊戲」,第一步當然就是要先造出能夠描述細胞的類別。假設類別的名稱是Cell,而屬性有細胞的狀態、位置、尺寸等,則Cell類別可以這樣設計:

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

在非物件導向式的寫法中,我們是用兩個2D的list來記錄細胞在當前及下一世代時的狀態。在使用物件導向式寫法時,我們稍微改變一下,改成記錄細胞在當前及前一世代時的狀態。要注意的是,因應這樣子的改變,計算細胞當前狀態值的寫法也必須跟著調整;至於要怎麼個調整法,後面再來詳細說明。

先前提到過,利用物件導向式的寫法,可以創造出更多的可能性。例如,我們可以讓細胞在改變狀態時,呈現出有別於黑、白這兩種分別代表生、死的顏色。假設我們要讓細胞在活過來時變成藍色,而由生轉死的時候變成紅色,只要在Cell類別中加入下面這個方法就可以了:

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)

有了Cell這個類別之後,就可以造出一個個的細胞物件,並讓它們排列成棋盤狀。不過,這一個個的細胞物件,要用什麼樣的資料結構來處理呢?答案很簡單,就把它們放進一個2D的list中就可以了;這跟前面非物件導向式的寫法其實也沒什麼太大的不同。非物件導向式的寫法是把細胞的狀態放到2D的list中,而物件導向式的寫法,則是把細胞本身,也就是細胞物件,放到2D的list中。要把細胞物件放到2D的list中,程式可以這樣寫:

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

這樣子,board中的每個元素,就都是當前及前一世代的狀態值皆為0的細胞,而其排列的方式,也會對應到細胞實際上棋盤式的排列方式。

造好board這個2D list之後,要怎麼設定「生命遊戲」的初始樣式呢?假設邊緣細胞自始至終都是死的,而其他細胞的初始狀態則是隨機的;根據這樣子的設定,程式可以這樣寫:

for i in range(1, rows-1):
for j in range(1, columns-1):
board[i][j].previous = board[i][j].state = random.randint(0, 1)

這裡要注意的是,必須把previous這個屬性的值,設定成和初始狀態值一樣,這樣子在接下來進行第一次迭代的時候,才能夠利用它來計算細胞新的狀態值。

計算細胞新狀態值的寫法,其實和非物件導向式的寫法差不多;重點在於,用於計算的狀態值現在是存放在previous這個屬性中。假設現在要計算board[i][j]這個細胞的新狀態值,程式可以這樣寫:

# 計算活著的鄰居數量
neighbor_sum = sum(cell.previous for cell in board[i-1][j-1:j+2]) + sum(cell.previous for cell in board[i][j-1:j+2:2]) + sum(cell.previous for cell in board[i+1][j-1:j+2])

# 依據規則決定細胞的新狀態
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

利用上面提到的方式,就可以把「生命遊戲」改成以物件導向的方式來寫。下面的例子就是以物件導向方式寫成的「生命遊戲」。

Example 7.3: Object-Oriented Game of Life

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):
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)


# python version 3.10.9
import random
import sys

import pygame # version 2.3.0


pygame.init()

pygame.display.set_caption("Example 7.3: Object-Oriented Game of Life")

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].previous = board[i][j].state = random.randint(0, 1)

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.previous for cell in board[i-1][j-1:j+2]) + \
sum(cell.previous for cell in board[i][j-1:j+2:2]) + \
sum(cell.previous for cell in board[i+1][j-1:j+2])

# 依據規則決定細胞的新狀態
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

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

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






留言
avatar-img
留言分享你的想法!
avatar-img
ysf的沙龍
17會員
145內容數
寫點東西自娛娛人
ysf的沙龍的其他內容
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
2025/03/31
這節介紹Wolfram的基礎CA (elementary CA)設計方式,以及其迭代過程所構成的圖案。
Thumbnail
2025/03/31
這節介紹Wolfram的基礎CA (elementary CA)設計方式,以及其迭代過程所構成的圖案。
Thumbnail
看更多
你可能也想看
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
沙龍一直是創作與交流的重要空間,這次 vocus 全面改版了沙龍介面,就是為了讓好內容被好好看見! 你可以自由編排你的沙龍首頁版位,新版手機介面也讓每位訪客都能更快找到感興趣的內容、成為你的支持者。 改版完成後可以在社群媒體分享新版面,並標記 @vocus.official⁠ ♥️ ⁠
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
這篇內容,將會用一個簡單的範例,來解釋物件(Object)和實體(Instance)的差別。包括Instance的簡介、ID、物件改變會影響實體。
Thumbnail
這篇內容,將會用一個簡單的範例,來解釋物件(Object)和實體(Instance)的差別。包括Instance的簡介、ID、物件改變會影響實體。
Thumbnail
這一章節旨在介紹 PHP 中的物件導向編程(OOP)概念。通過詳細講解類別、建構子、訪問修飾符(公開、私有、受保護)、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等概念,使讀者能夠理解和應用這些 OOP 技術來編寫更具結構性和可維護性的 PHP 代碼。
Thumbnail
這一章節旨在介紹 PHP 中的物件導向編程(OOP)概念。通過詳細講解類別、建構子、訪問修飾符(公開、私有、受保護)、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等概念,使讀者能夠理解和應用這些 OOP 技術來編寫更具結構性和可維護性的 PHP 代碼。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
一切從"物件(Object)"開始 1.建立新物件 2.編輯物件內容 3.在物件中新增區塊
Thumbnail
一切從"物件(Object)"開始 1.建立新物件 2.編輯物件內容 3.在物件中新增區塊
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
本章節旨在介紹JavaScript中的物件導向編程。內容包括類別(Class)的定義和使用,建構子的作用,以及公開,私有,受保護(Protected)等不同訪問修飾符的概念。此外,還涵蓋了繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型、反射等物件導向的主要觀念。
Thumbnail
這一節談的是用物件導向程式設計(object-oriented programming, OOP)的方式來實作隨機漫步。
Thumbnail
這一節談的是用物件導向程式設計(object-oriented programming, OOP)的方式來實作隨機漫步。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
本章節的目的是讓讀者瞭解C#的物件導向特性,包括類別、繼承、多型、封裝等基本概念,以及介面、抽象類別、靜態類別等進階主題。此外,本章節也將介紹如何使用列舉、委派、Lambda表達式、泛型及反射,這些都是C#中常見的強大功能。
Thumbnail
本文介紹了Python中的物件導向程式設計的重要概念,包括類別、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射。每個概念都有對應的程式碼範例來說明其用法和功能。這些概念對於理解和使用Python進行物件導向程式設計至關重要。
Thumbnail
本文介紹了Python中的物件導向程式設計的重要概念,包括類別、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda表達式、泛型和反射。每個概念都有對應的程式碼範例來說明其用法和功能。這些概念對於理解和使用Python進行物件導向程式設計至關重要。
Thumbnail
進入物件導向設計的實戰階段,我們通過建立人力資源管理功能來實踐理論知識。透過這些實作練習,能夠深化對物件導向概念的理解,並學會如何在實際開發中應用這些概念。
Thumbnail
進入物件導向設計的實戰階段,我們通過建立人力資源管理功能來實踐理論知識。透過這些實作練習,能夠深化對物件導向概念的理解,並學會如何在實際開發中應用這些概念。
Thumbnail
本階段深掘PHP中類別與物件的應用,從基本定義到屬性與方法的運用,並特別著重於訪問控制和靜態成員的概念。學生將學會如何有效地利用公開、保護、私有屬性,以及如何在不實例化的情況下透過類別名稱直接訪問靜態屬性和方法,進一步鞏固物件導向程式設計的核心知識。
Thumbnail
本階段深掘PHP中類別與物件的應用,從基本定義到屬性與方法的運用,並特別著重於訪問控制和靜態成員的概念。學生將學會如何有效地利用公開、保護、私有屬性,以及如何在不實例化的情況下透過類別名稱直接訪問靜態屬性和方法,進一步鞏固物件導向程式設計的核心知識。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News