Python に Curses ライブラリがあったので，シンプルなライフゲームを作ってみるテスト

# 1.この記事を読んで学べること・学べないこと
学べること
- Python における Curses 利用の初歩の初歩。
- ライフゲーム製作の一例。

学べないこと
- 優れたPython コードの書き方。
- 優れたライフゲームのアルゴリズム。

# 2.実際のコード

```py3:lifegame.py
class Life:

    def __init__(self, state=False):
        """
        State means whether the life is alive or not.
        Count means how many lives there are around the life.
        Survive means whether the life survives this turn.
        """
        self.count = 0
        self.state = state
        self.survive = False
    
    def rule(self):
        """ The core rule of this life game. """
        if self.state:
            if self.count in {2,3}:
                self.survive = True
            else:
                self.survive = False
        else:
            if self.count is 3:
                self.survive = True
            else:
                self.survive = False

    def update(self):
        """ Return True or False.
        Update the state of the life after checking if it will
        survive or not.
        """
        self.state = self.survive
        self.count = 0
        return self.survive

class Environment(list):

    def __init__(self, directory):
        """ Initialize the array with loaded text data. """
        self.life_count = 0
        text_env = self.load_from(directory)
        super().__init__(self)
        for y in range(self.height):
            self.append([])
            for x in range(self.width):
                if self.is_there_a_life_in( text_env[y][x] ):
                    self[y].append(Life(True))
                else:
                    self[y].append(Life(False))

    def is_there_a_life_in(self, string):
        """ Return True or False.
        Text data to load must be composed of two characters '*' and
        '_'. '*' means there is life. '_' means life is dead.
        """
        if string is "*":
            return True
        elif string in {"_", None}:
            return False
        else:
            raise TypeError("Undefined Char Used.")

    def load_from(self, directory):
        """ Return list of string.
        Load text data from file and calculate size of where lives
        are simulated.
        """
        self.width = 0
        text_env = []
        for char_list in self.parse_map(directory, "r"):
            if self.width < len(char_list):
                self.width = len(char_list)
            text_env.append(char_list)
        self.height = len(text_env)
        return text_env

    def parse_map(self, directory, mode):
        """ Strips '\n' off read line. """
        for line in open(directory, mode):
            yield list(line.rstrip('\n'))

    def how_many_lives(self):
        """ Count up the lives around the life. """
        for y in range(self.height):
            for x in range(self.width):
                for j, i in self.surround(y, x):
                    if self[j][i].state:
                        self[y][x].count += 1
                self[y][x].rule()

    def surround(self, y, x):
        """ Generate coordinates around the life. """
        from itertools import product
        Y = ( y-1, y, y+1 )
        X = ( x-1, x, x+1 )
        for j,i in product(Y, X):
            j = j % self.height
            i = i % self.width
            if (j, i) == (y, x):
                continue
            else:
                yield (j, i)

    def update(self):
        """ Update each cell and count the number of life in the
        Environment.
        """
        self.life_count = 0
        for y in range(self.height):
            for x in range(self.width):
                if self[y][x].update():
                    self.life_count += 1
                
class LifeGame:

    def __init__(self, directory):
        self.env = Environment(directory)
        self.turn = 0

        from curses import wrapper
        wrapper(self.main) # main メソッドを stdscr オブジェクトを与えて呼び出す

    def next_turn(self):
        self.env.how_many_lives()
        self.env.update()

    def show_status(self, stdscr):
        """ Print number of life in the environment and how many
        turn has passed.
        """
        stdscr.addstr( 3, self.env.width+3,
                "LifeCount: %d" % self.env.life_count)
        stdscr.addstr( 5, self.env.width+3,
                "Generation: %d" % self.turn)

    def main(self,stdscr):

        while True:
            stdscr.clear()　# コンソール画面の初期化（黒板を消すの感じ）
            for y in range(self.env.height):
                for x in range(self.env.width):
                    if self.env[y][x].state:
                        stdscr.addstr( y, x, "*" ) # コンソールに出力するメソッド
                    else:
                        stdscr.addstr( y, x, "_" )
            self.show_status(stdscr)
            stdscr.refresh() # 出力内容の反映
            if stdscr.getch(): # Enter 入力待ち
                self.next_turn()
                self.turn += 1

if __name__=='__main__':

    LifeGame("./map.txt")

```

これに例えば，

```text:map.txt
_*_
__*
***
```

こんなデータを与えてあげると，Enter を押すごとに変わりゆく様子を観察できる。

# 3.雑感
　前に Ruby で Curses に挑んだときは挫折してしまったのだけれど，Python でやってみたらなんかできちゃった。Python が素敵なのか，その頃より少し技術力が上がったのか...