[ATBS2nd]Chương 1 - Python cơ bản - Phần 16

Chào các bạn,
Chúng ta đến với phần cuối cùng của danh sách(list), là một chương trình ngắn và bài tập.
Chương trình ngắn: Trò chơi cuộc sống của Conway
Trò chơi cuộc sống của Conway là một ví dụ về automata di động: một bộ quy tắc chi phối hành vi của một lĩnh vực được tạo thành từ các tế bào rời rạc. Trong thực tế, nó tạo ra một hình ảnh động đẹp để xem xét. Bạn có thể vẽ từng bước trên giấy biểu đồ, sử dụng các ô vuông. Một hình vuông đen sẽ là những người còn sống và một hình vuông trắng sẽ bị chết. Nếu một nơi sống có hai hoặc ba người hàng xóm sống, nó tiếp tục sống ở bước tiếp theo. Nếu một nơi chết có chính xác ba người hàng xóm còn sống, nó sẽ được sinh ra ở bước tiếp theo. Mỗi hình vuông khác chết hoặc vẫn còn chết ở bước tiếp theo. Bạn có thể thấy một ví dụ về sự tiến triển của các bước trong hình dưới đây.


Để có thể tìm hiểu và hiểu rõ hơn về luật chơi của trò chơi bạn có thể tham khảo trên wiki, tại đây, và tại đây.
Mặc dù các quy tắc rất đơn giản, có nhiều hành vi đáng ngạc nhiên mà nổi lên. Các mô hình trong trò chơi cuộc sống của Conway có thể di chuyển, tự sao chép hoặc thậm chí bắt chước CPU. Nhưng nền tảng của tất cả các hành vi phức tạp, tiên tiến này là một chương trình khá đơn giản.
Chúng ta có thể sử dụng danh sách của danh sách để thể hiện trường hai chiều. Danh sách bên trong đại diện cho từng cột hình vuông và lưu trữ chuỗi băm ‘#’ cho hình vuông sống và chuỗi không gian ’ ’ cho hình vuông đã chết. Nhập mã nguồn sau vào trình chỉnh sửa tệp và lưu tệp dưới dạng conway.py. Nó rất tốt nếu bạn không hiểu cách thức hoạt động của tất cả các mã; chỉ cần nhập nó và làm theo cùng với các bình luận và giải thích được cung cấp ở đây:

# Conway's Game of Life
import random, time, copy
WIDTH = 60
HEIGHT = 20
# Create a list of list for the cells:
nextCells = []
for x in range(WIDTH):
	column = [] # Create a new column.
	for y in range(HEIGHT):
		if random.randint(0, 1) == 0:
			column.append('#') # Add a living cell.
		else:
			column.append(' ') # Add a dead cell.
	nextCells.append(column) # nextCells is a list of column lists.
	
while True: # Main program loop.
	print('\n\n\n\n\n') # Separate each step with newlines.
	currentCells = copy.deepcopy(nextCells)
	# Print currentCells on the screen:
	for y in range(HEIGHT):
		for x in range(WIDTH):
			print(currentCells[x][y], end='') # Print the # or space.
		print() # Print a newline at the end of the row.
	# Calculate the next step's cells based on current step's cells:
	for x in range(WIDTH):
		for y in range(HEIGHT):
			# Get neighboring coordinates:
			# `% WIDTH` ensures leftCoord is always between 0 and WIDTH - 1
			leftCoord = (x - 1) % WIDTH
			rightCoord = (x + 1) % WIDTH
			aboveCoord = (y - 1) % HEIGHT
			belowCoord = (y + 1) % HEIGHT
			# Count number of living neighbors:
			numNeighbors = 0
			if currentCells[leftCoord][aboveCoord] == '#':
				numNeighbors += 1 # Top-left neighbor is alive.
			if currentCells[x][aboveCoord] == '#':
				numNeighbors += 1 # Top neighbor is alive.
			if currentCells[rightCoord][aboveCoord] == '#':
				numNeighbors += 1 # Top-right neighbor is alive.
			if currentCells[leftCoord][y] == '#':
				numNeighbors += 1 # Left neighbor is alive.
			if currentCells[rightCoord][y] == '#':
				numNeighbors += 1 # Right neighbor is alive.
			if currentCells[leftCoord][belowCoord] == '#':
				numNeighbors += 1 # Bottom-left neighbor is alive.
			if currentCells[x][belowCoord] == '#':
				numNeighbors += 1 # Bottom neighbor is alive.
			if currentCells[rightCoord][belowCoord] == '#':
				numNeighbors += 1 # Bottom-right neighbor is alive.
			# Set cell based on Conway's Game of Life rules:
			if currentCells[x][y] == '#' and (numNeighbors == 2 or numNeighbors == 3):
			# Living cells with 2 or 3 neighbors stay alive:
				nextCells[x][y] = '#'
			elif currentCells[x][y] == ' ' and numNeighbors == 3:
			# Dead cells with 3 neighbors become alive:
				nextCells[x][y] = '#'
			else:
			# Everything else dies or stays dead:
				nextCells[x][y] = ' '
	time.sleep(1) # Add a 1-second pause to reduce flickering.

Bây giờ chúng ta sẽ phân tích từ đoạn code. Bắt đầu với dòng đầu tiên.

# Conway's Game of Life
import random, time, copy
WIDTH = 60
HEIGHT = 20

Đầu tiên chúng ta import các module để sử dụng các hàm sau random.randint(), time.sleep(), và copy.deepcopy().

# Create a list of list for the cells:
nextCells = []
for x in range(WIDTH):
	column = [] # Create a new column.
	for y in range(HEIGHT):
		if random.randint(0, 1) == 0:
			column.append('#') # Add a living cell.
		else:
			column.append(' ') # Add a dead cell.
	nextCells.append(column) # nextCells is a list of column lists. 

Bước đầu tiên của automata di động của chúng tôi sẽ hoàn toàn ngẫu nhiên. Chúng ta cần tạo một danh sách cấu trúc dữ liệu danh sách để lưu trữ các chuỗi ‘#’ và ‘’ đại diện cho một tế bào sống hoặc chết và vị trí của chúng trong danh sách danh sách phản ánh vị trí của chúng trên màn hình. Mỗi danh sách bên trong đại diện cho một cột của các ô. Cuộc gọi random.randint(0, 1) mang lại cơ hội thậm chí 50/50 giữa các tế bào bắt đầu sống hoặc chết.
Chúng ta đặt danh sách các danh sách này trong một biến gọi là nextCells, bởi vì danh sách đầu tiên bước trong vòng lặp chương trình chính của chúng tôi sẽ là sao chép nextCells vào currentCells. Đối với danh sách cấu trúc dữ liệu danh sách của chúng ta, tọa độ x bắt đầu từ 0 ở bên trái và tăng dần sang phải, trong khi tọa độ y bắt đầu từ 0 ở trên cùng và tăng dần xuống. Vì vậy nextCells [0] [0] sẽ đại diện cho ô ở phía trên bên trái màn hình, trong khi nextCells [1] [0] đại diện cho ô bên phải của ô đó và nextCells [0] [1] đại diện cho ô bên dưới nó.

while True: # Main program loop.
	print('\n\n\n\n\n') # Separate each step with newlines.
	currentCells = copy.deepcopy(nextCells)

Mỗi lần lặp của vòng lặp chương trình chính của chúng ta sẽ là một bước duy nhất của automata di động. Trên mỗi bước, chúng ta sẽ sao chép nextCells sang currentCells, in currentCells trên màn hình và sau đó sử dụng các ô trong currentCells để tính toán các ô trong nextCells.

# Print currentCells on the screen:
	for y in range(HEIGHT):
		for x in range(WIDTH):
			print(currentCells[x][y], end='') # Print the # or space.
		print() # Print a newline at the end of the row.

Các vòng lặp lồng nhau này đảm bảo rằng chúng tôi in một hàng ô đầy đủ lên màn hình, theo sau là một ký tự dòng mới ở cuối hàng. Chúng tôi lặp lại điều này cho mỗi hàng trong nextCells.

# Calculate the next step's cells based on current step's cells:
	for x in range(WIDTH):
		for y in range(HEIGHT):
			# Get neighboring coordinates:
			# `% WIDTH` ensures leftCoord is always between 0 and WIDTH - 1
			leftCoord = (x - 1) % WIDTH
			rightCoord = (x + 1) % WIDTH
			aboveCoord = (y - 1) % HEIGHT
			belowCoord = (y + 1) % HEIGHT

Tiếp theo, chúng ta cần sử dụng hai vòng lặp lồng nhau để tính toán từng ô cho bước tiếp theo. Trạng thái sống hay chết của ô phụ thuộc vào các lân cận, vì vậy trước tiên, hãy tính toán chỉ số của các ô ở bên trái, phải, trên và dưới tọa độ x và y hiện tại.
Toán tử% mod thực hiện một gói dữ liệu. Hàng xóm bên trái của một ô trong cột ngoài cùng bên trái 0 sẽ là 0 - 1 hoặc -1. Để kết thúc điều này với chỉ số cột ngoài cùng bên phải, 59, chúng tôi tính toán (0 - 1)% WIDTH. Vì WIDTH là 60, biểu thức này ước tính là 59. Kỹ thuật bao quanh mod này cũng hoạt động cho các hàng xóm bên phải, bên trên và bên dưới.

# Count number of living neighbors:
			numNeighbors = 0
			if currentCells[leftCoord][aboveCoord] == '#':
				numNeighbors += 1 # Top-left neighbor is alive.
			if currentCells[x][aboveCoord] == '#':
				numNeighbors += 1 # Top neighbor is alive.
			if currentCells[rightCoord][aboveCoord] == '#':
				numNeighbors += 1 # Top-right neighbor is alive.
			if currentCells[leftCoord][y] == '#':
				numNeighbors += 1 # Left neighbor is alive.
			if currentCells[rightCoord][y] == '#':
				numNeighbors += 1 # Right neighbor is alive.
			if currentCells[leftCoord][belowCoord] == '#':
				numNeighbors += 1 # Bottom-left neighbor is alive.
			if currentCells[x][belowCoord] == '#':
				numNeighbors += 1 # Bottom neighbor is alive.
			if currentCells[rightCoord][belowCoord] == '#':
				numNeighbors += 1 # Bottom-right neighbor is alive.

Để quyết định xem ô tại nextCells [x] [y] nên sống hay chết, chúng ta cần đếm số lượng hàng xóm sống hiện tại Cells [x] [y] có. Chuỗi các câu lệnh if này kiểm tra từng trong số tám hàng xóm của ô này và thêm 1 đến numNeighbor cho mỗi người sống.

# Set cell based on Conway's Game of Life rules:
			if currentCells[x][y] == '#' and (numNeighbors == 2 or numNeighbors == 3):
			# Living cells with 2 or 3 neighbors stay alive:
				nextCells[x][y] = '#'
			elif currentCells[x][y] == ' ' and numNeighbors == 3:
			# Dead cells with 3 neighbors become alive:
				nextCells[x][y] = '#'
			else:
			# Everything else dies or stays dead:
				nextCells[x][y] = ' '
	time.sleep(1) # Add a 1-second pause to reduce flickering.

Bây giờ chúng ta biết số lượng hàng xóm sống cho các tế bào tại currentCells [x] [y], chúng ta có thể đặt nextCells [x] [y] thành ‘#’ hoặc ’ '. Sau khi chúng tôi lặp qua mọi tọa độ x và y có thể, chương trình sẽ tạm dừng 1 giây bằng cách gọi time.sleep (1). Sau đó, việc thực hiện chương trình quay trở lại bắt đầu vòng lặp chương trình chính để tiếp tục với bước tiếp theo. Một số mô hình đã được phát hiện với những cái tên như tàu lượn siêu tốc, tàu cánh quạt, thời gian, hoặc tàu vũ trụ hạng nặng. Mẫu tàu lượn, được mô tả trong hình dưới đây, kết quả là một mẫu mà di chuyển theo đường chéo cứ sau bốn bước. Bạn có thể tạo một tàu lượn bằng cách thay thế dòng này trong chương trình conway.py của chúng tôi
`

if random.randint(0, 1) == 0:

Thay bằng dòng

if (x, y) in ((1, 0), (2, 1), (0, 2), (1, 2), (2, 2)):

`
Bạn có thể tìm hiểu thêm về các thiết bị hấp dẫn được tạo bằng cách sử dụng trò chơi cuộc sống của Conway bằng cách tìm kiếm trên web. Và bạn có thể tìm thấy các chương trình Python ngắn, dựa trên văn bản khác như chương trình này tại https://github.com/asweigart/pythonstdiogames.
Tổng kết
Danh sách là các loại dữ liệu hữu ích vì chúng cho phép bạn viết mã hoạt động trên một số giá trị có thể sửa đổi trong một biến. Sau này trong series này, bạn sẽ thấy các chương trình sử dụng danh sách để làm những việc khó hoặc không thể làm nếu không có chúng.
Danh sách là một kiểu dữ liệu chuỗi có thể thay đổi, có nghĩa là nội dung có thể thay đổi. Tuples và chuỗi, mặc dù cũng có kiểu dữ liệu chuỗi, là bất biến và không thể thay đổi. Một biến chứa giá trị tuple hoặc chuỗi có thể được ghi đè bằng giá trị tuple hoặc chuỗi mới, nhưng điều này không giống với việc sửa đổi giá trị hiện tại ở vị trí như các phương thức append () hoặc remove () thực hiện trên danh sách.
Các biến không lưu trữ giá trị danh sách trực tiếp; họ lưu trữ tham chiếu cho danh sách. Đây là một sự khác biệt quan trọng khi bạn sao chép các biến hoặc chuyển danh sách dưới dạng đối số trong các lệnh gọi hàm. Vì giá trị đang được sao chép là tham chiếu danh sách, hãy lưu ý rằng mọi thay đổi bạn thực hiện đối với danh sách có thể ảnh hưởng đến một biến khác trong chương trình của bạn. Bạn có thể sử dụng copy () hoặc deepcopy () nếu bạn muốn thay đổi danh sách trong một biến mà không sửa đổi danh sách gốc.
Người dịch: Hungdh

2 Likes