生命游戏
生命游戏的宇宙是一个无限的,其中细胞的二维正交网格,每个细胞处于两种可能的状态之一,即*活着*或*死亡*(分别是*人口稠密*和*无人居住*)。每个细胞与它的八个邻居相互作用,这八个邻居是水平,垂直或对角相邻的细胞。在每一步中,都会发生以下转换:
- 任何有两个以上活着的邻居的活细胞都会死亡,好像是在人口下一样。
- 任何有两三个活着的邻居的活细胞都会生活在下一代。
- 任何有三个以上活着的邻居的活细胞都会死亡,就好像人口过剩一样。
- 任何具有三个活的邻居的死细胞都会变成一个活细胞,就像是通过繁殖一样。
其简单动画效果如:
其主要实现逻辑代码出自Effective Python一书中。不过原代码中的生命游戏是静止的,把每一代分别打印出来,没有动画效果,我增加部分代码,实现在终端的动画效果。
动画实现原理是:
\x1b[nA] 光标上移
\x1b[nB] 光标下移
\x1b[nC] 光标右移
\x1b[nD] 光标左移
(n为字符数)控制光标位置是通过ANSI转义符实现的。从这篇文章获得相关知识的:https://www.zhihu.com/question/21100416/answer/208143599
第一代细胞(预设生存环境在 X * Y 的二维平面方格上)随机生成,将其打印在控制台上,然后此时控制台光标会从初始位置(方格左上角(1,1)上)到方格右下角(X,Y)的位置。下一代细胞打印前通过移动控制台的光标到初始位置(1,1)上,此后的打印这代细胞就会覆盖前一代细胞。造成视觉上的动画效果。
全部代码如下:
import os
import sys
import time
import random
from collections import namedtuple ALIVE = '*'
EMPTY = ' ' Query = namedtuple('Query', ('y', 'x')) def count_neighbors(y, x):
n_ = yield Query(y + 1, x + 0) # North
ne = yield Query(y + 1, x + 1) # Northeast
e_ = yield Query(y + 0, x + 1) # East
se = yield Query(y - 1, x + 1) # Southeast
s_ = yield Query(y - 1, x + 0) # South
sw = yield Query(y - 1, x - 1) # Southwest
w_ = yield Query(y + 0, x - 1) # West
nw = yield Query(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 Transition = namedtuple('Transition', ('y', 'x', 'state')) def step_cell(y, x):
state = yield Query(y, x)
neighbors = yield from count_neighbors(y, x)
next_state = game_logic(state, neighbors)
yield Transition(y, x, next_state) def game_logic(state, neighbors):
if state == ALIVE:
if neighbors < 2:
return EMPTY # Die: Too few
elif neighbors > 3:
return EMPTY # Die: Too many
else:
if neighbors == 3:
return ALIVE # Regenerate
return state TICK = object() def simulate(height, width):
while True:
for y in range(height):
for x in range(width):
yield from step_cell(y, x)
yield TICK class Grid(object):
def __init__(self, height, width):
self.height = height
self.width = width
self.rows = []
for _ in range(self.height):
self.rows.append([EMPTY] * self.width) def query(self, y, x):
return self.rows[y % self.height][x % self.width] def assign(self, y, x, state):
self.rows[y % self.height][x % self.width] = state def random_alive(self, live_count):
xy = [(i,j) for i in range(self.width) for j in range(self.height)]
for i,j in random.sample(xy, live_count):
self.assign(i, j, ALIVE) def live_a_generation(self,grid, sim):
# self.change_state(EMPTY)
progeny = Grid(grid.height, grid.width)
item = next(sim)
while item is not TICK:
if isinstance(item, Query):
state = grid.query(item.y, item.x)
item = sim.send(state)
else: # Must be a Transition
progeny.assign(item.y, item.x, item.state)
item = next(sim)
return progeny def __str__(self):
output = ''
for row in self.rows:
for cell in row:
output += cell
output += '\n'
return output.strip() def main(x,y,k):
os.system('cls') # linux 为 clear
grid = Grid(x, y)
grid.random_alive(k)
clear = '\x1b[{}A\x1b[{}D'.format(x,y)
print(grid, end='')
sim = simulate(grid.height, grid.width)
while 1:
time.sleep(.1)
grid = grid.live_a_generation(grid, sim)
print(clear)
print(grid, end='')
time.sleep(.1)
print(clear) if __name__ == '__main__':
main(30,40,205)