生命游戲
生命游戲的宇宙是一個無限的,其中細胞的二維正交網格,每個細胞處於兩種可能的狀態之一,即*活着*或*死亡*(分別是*人口稠密*和*無人居住*)。每個細胞與它的八個鄰居相互作用,這八個鄰居是水平,垂直或對角相鄰的細胞。在每一步中,都會發生以下轉換:
- 任何有兩個以上活着的鄰居的活細胞都會死亡,好像是在人口下一樣。
- 任何有兩三個活着的鄰居的活細胞都會生活在下一代。
- 任何有三個以上活着的鄰居的活細胞都會死亡,就好像人口過剩一樣。
- 任何具有三個活的鄰居的死細胞都會變成一個活細胞,就像是通過繁殖一樣。
其簡單動畫效果如:
其主要實現邏輯代碼出自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)上,此后的打印這代細胞就會覆蓋前一代細胞。造成視覺上的動畫效果。
全部代碼如下:
1 import os 2 import sys 3 import time 4 import random 5 from collections import namedtuple 6 7 8 ALIVE = '*' 9 EMPTY = ' ' 10 11 12 Query = namedtuple('Query', ('y', 'x')) 13 14 def count_neighbors(y, x): 15 n_ = yield Query(y + 1, x + 0) # North 16 ne = yield Query(y + 1, x + 1) # Northeast 17 e_ = yield Query(y + 0, x + 1) # East 18 se = yield Query(y - 1, x + 1) # Southeast 19 s_ = yield Query(y - 1, x + 0) # South 20 sw = yield Query(y - 1, x - 1) # Southwest 21 w_ = yield Query(y + 0, x - 1) # West 22 nw = yield Query(y + 1, x - 1) # Northwest 23 neighbor_states = [n_, ne, e_, se, s_, sw, w_, nw] 24 count = 0 25 for state in neighbor_states: 26 if state == ALIVE: 27 count += 1 28 return count 29 30 Transition = namedtuple('Transition', ('y', 'x', 'state')) 31 32 def step_cell(y, x): 33 state = yield Query(y, x) 34 neighbors = yield from count_neighbors(y, x) 35 next_state = game_logic(state, neighbors) 36 yield Transition(y, x, next_state) 37 38 39 def game_logic(state, neighbors): 40 if state == ALIVE: 41 if neighbors < 2: 42 return EMPTY # Die: Too few 43 elif neighbors > 3: 44 return EMPTY # Die: Too many 45 else: 46 if neighbors == 3: 47 return ALIVE # Regenerate 48 return state 49 50 51 TICK = object() 52 53 def simulate(height, width): 54 while True: 55 for y in range(height): 56 for x in range(width): 57 yield from step_cell(y, x) 58 yield TICK 59 60 61 class Grid(object): 62 def __init__(self, height, width): 63 self.height = height 64 self.width = width 65 self.rows = [] 66 for _ in range(self.height): 67 self.rows.append([EMPTY] * self.width) 68 69 def query(self, y, x): 70 return self.rows[y % self.height][x % self.width] 71 72 def assign(self, y, x, state): 73 self.rows[y % self.height][x % self.width] = state 74 75 def random_alive(self, live_count): 76 xy = [(i,j) for i in range(self.width) for j in range(self.height)] 77 for i,j in random.sample(xy, live_count): 78 self.assign(i, j, ALIVE) 79 80 def live_a_generation(self,grid, sim): 81 # self.change_state(EMPTY) 82 progeny = Grid(grid.height, grid.width) 83 item = next(sim) 84 while item is not TICK: 85 if isinstance(item, Query): 86 state = grid.query(item.y, item.x) 87 item = sim.send(state) 88 else: # Must be a Transition 89 progeny.assign(item.y, item.x, item.state) 90 item = next(sim) 91 return progeny 92 93 def __str__(self): 94 output = '' 95 for row in self.rows: 96 for cell in row: 97 output += cell 98 output += '\n' 99 return output.strip() 100 101 102 def main(x,y,k): 103 os.system('cls') # linux 為 clear 104 grid = Grid(x, y) 105 grid.random_alive(k) 106 clear = '\x1b[{}A\x1b[{}D'.format(x,y) 107 print(grid, end='') 108 sim = simulate(grid.height, grid.width) 109 while 1: 110 time.sleep(.1) 111 grid = grid.live_a_generation(grid, sim) 112 print(clear) 113 print(grid, end='') 114 time.sleep(.1) 115 print(clear) 116 117 if __name__ == '__main__': 118 main(30,40,205)