LoginSignup
5
6

More than 5 years have passed since last update.

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

Posted at

1.この記事を読んで学べること・学べないこと

学べること
- Python における Curses 利用の初歩の初歩。
- ライフゲーム製作の一例。

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

2.実際のコード

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")

これに例えば,

map.txt
_*_
__*
***

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

3.雑感

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

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6