Python简单动画——生命游戏

2019-08-08  本文已影响0人  何物昂

生命游戏

生命游戏的宇宙是一个无限的,其中细胞的二维正交网格,每个细胞处于两种可能的状态之一,即活着或死亡(分别是人口稠密和无人居住)。每个细胞与它的八个邻居相互作用,这八个邻居是水平,垂直或对角相邻的细胞。在每一步中,都会发生以下转换:

其简单动画效果如:


life-game.gif

代码实现

代码逻辑

其主要实现逻辑代码出自Effective Python一书中。不过原代码中的生命游戏是静止的,把每一代分别打印出来,没有动画效果,我增加部分代码,实现在终端的动画效果。

动画效果

第一代细胞(预设生存环境在 X * Y 的二维平面方格上)随机生成,将其打印在控制台上,然后此时控制台光标会从初始位置(方格左上角(1,1)上)到方格右下角(X,Y)的位置。下一代细胞打印前通过移动控制台的光标到初始位置(1,1)上,此后的打印这代细胞就会覆盖前一代细胞。造成视觉上的动画效果。

控制光标位置是通过ANSI转义符实现的,参考这个知乎回答

(n为字符数)
\x1b[nA]   光标上移
\x1b[nB]   光标下移
\x1b[nC]   光标右移
\x1b[nD]   光标左移

代码

全部代码(懒得写注释了,游戏逻辑可以参考Effective Python)

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)
上一篇下一篇

猜你喜欢

热点阅读