Skip to content

Instantly share code, notes, and snippets.

@lseongjoo
Forked from ramalho/coro_life.py
Created June 7, 2018 05:34
Show Gist options
  • Select an option

  • Save lseongjoo/c3bb575c40da2fa9b8a5ffb2e56ca8c1 to your computer and use it in GitHub Desktop.

Select an option

Save lseongjoo/c3bb575c40da2fa9b8a5ffb2e56ca8c1 to your computer and use it in GitHub Desktop.

Revisions

  1. @ramalho ramalho revised this gist Feb 19, 2015. 1 changed file with 3 additions and 4 deletions.
    7 changes: 3 additions & 4 deletions coro_life.py
    Original file line number Diff line number Diff line change
    @@ -192,9 +192,9 @@
    EMPTY = '-'
    TICK = object()

    Query = namedtuple('Query', ('y', 'x'))
    Query = namedtuple('Query', 'y x')

    Transition = namedtuple('Transition', ('y', 'x', 'state'))
    Transition = namedtuple('Transition', 'y x state')


    def count_neighbors(y, x):
    @@ -295,8 +295,7 @@ def __str__(self):
    for i, data in enumerate(self.columns):
    line = data.splitlines()[max(0, j - 1)]
    if j == 0:
    padding = ' ' * (len(line) // 2)
    rows[j] += padding + str(i) + padding
    rows[j] += str(i).center(len(line))
    else:
    rows[j] += line
    if (i + 1) < len(self.columns):
  2. @ramalho ramalho revised this gist Feb 19, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion coro_life.py
    Original file line number Diff line number Diff line change
    @@ -69,7 +69,7 @@
    >>> it.send(ALIVE) # Send q2 state, get q3
    Query(y=10, x=6)
    >>> # Send q3 ... q7 states, get q4 ... q8
    >>> [it.send(EMPTY) for i in range(3, 8)] # doctest: +ELLIPSIS
    >>> [it.send(state) for state in (EMPTY)*5] # doctest: +ELLIPSIS
    [Query(y=9, x=6), Query(y=9, x=5), ..., Query(y=11, x=4)]
    >>> try:
    ... it.send(EMPTY) # Send q8 state, drive coroutine to end
  3. @ramalho ramalho revised this gist Feb 19, 2015. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion .gitignore
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    __pycache__
  4. @ramalho ramalho revised this gist Feb 19, 2015. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions .gitignore
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    __pycache__
  5. @ramalho ramalho revised this gist Feb 19, 2015. 1 changed file with 11 additions and 12 deletions.
    23 changes: 11 additions & 12 deletions coro_life.py
    Original file line number Diff line number Diff line change
    @@ -22,13 +22,14 @@
    by Brett Slatkin
    This script was refactored by Luciano Ramalho from code listed in Item
    40 of Slatkin's excellent book "Effective Python: 59 Specific Ways to
    Write Better Python".
    40 of Brett Slatkin's excellent book "Effective Python: 59 Specific Ways
    to Write Better Python".
    The original code is published on Github under the Apache 2.0 license:
    https://github.com/bslatkin/effectivepython/blob/master/example_code/item_40.py
    The refactoring included:
    - replacing the top-level testing code with doctests;
    @@ -38,10 +39,12 @@
    - similarly, changing the ``Grid.assign`` method to ``Grid.__setitem__``;
    Running this script produces no output. To run the doctests, use::
    $ python3 -m doctest coro_life.py
    No output will be generated if all doctests pass. To see doctest output,
    use::
    @@ -85,7 +88,7 @@
    >>> it = step_cell(10, 5)
    >>> next(it) # Initial location query
    Query(y=10, x=5)
    >>> [it.send(state) for state in (ALIVE)*5 + (EMPTY)*3] # doctest: +ELLIPSIS
    >>> [it.send(st) for st in (ALIVE)*5 + (EMPTY)*3] # doctest: +ELLIPSIS
    [Query(y=11, x=5), Query(y=11, x=6), ... Query(y=11, x=4)]
    >>> it.send(EMPTY) # Send q8 state, get game decision
    Transition(y=10, x=5, state='-')
    @@ -134,8 +137,8 @@
    Introductory diagram
    ====================
    The first Game of Life example in Item 40 (with an added generation
    showing no remaining live cells)::
    The first Game of Life example in Item 40 of "Effective Python"
    (with an added generation showing zero surviving cells)::
    >>> grid = Grid(5, 5)
    >>> grid[1, 1] = ALIVE
    @@ -187,14 +190,16 @@

    ALIVE = '*'
    EMPTY = '-'
    TICK = object()

    Query = namedtuple('Query', ('y', 'x'))

    Transition = namedtuple('Transition', ('y', 'x', 'state'))


    def count_neighbors(y, x):
    n_ = yield Query(y + 1, x + 0) # North
    ne = yield Query(y + 1, x + 1) # Northeast
    # Define e_, se, s_, sw, w_, nw ...
    e_ = yield Query(y + 0, x + 1) # East
    se = yield Query(y - 1, x + 1) # Southeast
    s_ = yield Query(y - 1, x + 0) # South
    @@ -221,19 +226,13 @@ def game_logic(state, neighbors):
    return state


    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)


    TICK = object()


    def simulate(height, width):
    while True:
    for y in range(height):
  6. @ramalho ramalho revised this gist Feb 19, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion coro_life.py
    Original file line number Diff line number Diff line change
    @@ -45,7 +45,7 @@
    No output will be generated if all doctests pass. To see doctest output,
    use::
    $ python3 -m doctest coro_life.py
    $ python3 -m doctest coro_life.py -v
    --------------------------------------------
  7. @ramalho ramalho created this gist Feb 19, 2015.
    305 changes: 305 additions & 0 deletions coro_life.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,305 @@
    #!/usr/bin/env python3

    # Copyright 2014 Brett Slatkin, Pearson Education Inc.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    # http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    """
    ======================================================
    John Conway's Game of Life implemented with coroutines
    ======================================================
    by Brett Slatkin
    This script was refactored by Luciano Ramalho from code listed in Item
    40 of Slatkin's excellent book "Effective Python: 59 Specific Ways to
    Write Better Python".
    The original code is published on Github under the Apache 2.0 license:
    https://github.com/bslatkin/effectivepython/blob/master/example_code/item_40.py
    The refactoring included:
    - replacing the top-level testing code with doctests;
    - changing the ``Grid.query`` method to ``Grid.__getitem__``, taking a
    tuple of coordinates as argument;
    - similarly, changing the ``Grid.assign`` method to ``Grid.__setitem__``;
    Running this script produces no output. To run the doctests, use::
    $ python3 -m doctest coro_life.py
    No output will be generated if all doctests pass. To see doctest output,
    use::
    $ python3 -m doctest coro_life.py
    --------------------------------------------
    Doctests for specific components of the code
    --------------------------------------------
    Drive ``count_neighbors``
    =========================
    Drive the ``count_neighbors`` coroutine with fake data::
    >>> it = count_neighbors(10, 5)
    >>> next(it) # Get the first query, for q1
    Query(y=11, x=5)
    >>> it.send(ALIVE) # Send q1 state, get q2
    Query(y=11, x=6)
    >>> it.send(ALIVE) # Send q2 state, get q3
    Query(y=10, x=6)
    >>> # Send q3 ... q7 states, get q4 ... q8
    >>> [it.send(EMPTY) for i in range(3, 8)] # doctest: +ELLIPSIS
    [Query(y=9, x=6), Query(y=9, x=5), ..., Query(y=11, x=4)]
    >>> try:
    ... it.send(EMPTY) # Send q8 state, drive coroutine to end
    ... except StopIteration as e:
    ... count = e.value # Value from return statement
    ...
    >>> count
    2
    Drive ``step_cell``
    ===================
    Drive the ``step_cell`` coroutine with fake data::
    >>> it = step_cell(10, 5)
    >>> next(it) # Initial location query
    Query(y=10, x=5)
    >>> [it.send(state) for state in (ALIVE)*5 + (EMPTY)*3] # doctest: +ELLIPSIS
    [Query(y=11, x=5), Query(y=11, x=6), ... Query(y=11, x=4)]
    >>> it.send(EMPTY) # Send q8 state, get game decision
    Transition(y=10, x=5, state='-')
    Test ``Grid``
    =============
    Put a glider in a 5x9 grid:
    >>> grid = Grid(5, 9)
    >>> grid[0, 3] = ALIVE
    >>> grid[1, 4] = ALIVE
    >>> grid[2, 2] = ALIVE
    >>> grid[2, 3] = ALIVE
    >>> grid[2, 4] = ALIVE
    >>> print(grid)
    ---*-----
    ----*----
    --***----
    ---------
    ---------
    <BLANKLINE>
    Run the game for 5 generations
    ==============================
    Test ``ColumnPrinter``, ``simulate`` and ``live_a_generation``::
    >>> columns = ColumnPrinter()
    >>> sim = simulate(grid.height, grid.width)
    >>> for i in range(5):
    ... columns.append(str(grid))
    ... grid = live_a_generation(grid, sim)
    ...
    >>> print(columns) # doctest: +NORMALIZE_WHITESPACE
    0 | 1 | 2 | 3 | 4
    ---*----- | --------- | --------- | --------- | ---------
    ----*---- | --*-*---- | ----*---- | ---*----- | ----*----
    --***---- | ---**---- | --*-*---- | ----**--- | -----*---
    --------- | ---*----- | ---**---- | ---**---- | ---***---
    --------- | --------- | --------- | --------- | ---------
    Introductory diagram
    ====================
    The first Game of Life example in Item 40 (with an added generation
    showing no remaining live cells)::
    >>> grid = Grid(5, 5)
    >>> grid[1, 1] = ALIVE
    >>> grid[2, 2] = ALIVE
    >>> grid[2, 3] = ALIVE
    >>> grid[3, 3] = ALIVE
    >>> columns = ColumnPrinter()
    >>> sim = simulate(grid.height, grid.width)
    >>> for i in range(6):
    ... columns.append(str(grid))
    ... grid = live_a_generation(grid, sim)
    ...
    >>> print(columns) # doctest: +NORMALIZE_WHITESPACE
    0 | 1 | 2 | 3 | 4 | 5
    ----- | ----- | ----- | ----- | ----- | -----
    -*--- | --*-- | --**- | --*-- | ----- | -----
    --**- | --**- | -*--- | -*--- | -**-- | -----
    ---*- | --**- | --**- | --*-- | ----- | -----
    ----- | ----- | ----- | ----- | ----- | -----
    Blinker demo
    ============
    The blinker is the simplest oscillator::
    >>> grid = Grid(5, 5)
    >>> for i in range(1, 4):
    ... grid[2, i] = ALIVE
    ...
    >>> columns = ColumnPrinter()
    >>> sim = simulate(grid.height, grid.width)
    >>> for i in range(8):
    ... columns.append(str(grid))
    ... grid = live_a_generation(grid, sim)
    ...
    >>> print(columns) # doctest: +NORMALIZE_WHITESPACE
    0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
    ----- | ----- | ----- | ----- | ----- | ----- | ----- | -----
    ----- | --*-- | ----- | --*-- | ----- | --*-- | ----- | --*--
    -***- | --*-- | -***- | --*-- | -***- | --*-- | -***- | --*--
    ----- | --*-- | ----- | --*-- | ----- | --*-- | ----- | --*--
    ----- | ----- | ----- | ----- | ----- | ----- | ----- | -----
    """


    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
    # Define e_, se, s_, sw, w_, nw ...
    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


    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


    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)


    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 __str__(self):
    output = ''
    for row in self.rows:
    for cell in row:
    output += cell
    output += '\n'
    return output

    def __getitem__(self, position):
    y, x = position
    return self.rows[y % self.height][x % self.width]

    def __setitem__(self, position, state):
    y, x = position
    self.rows[y % self.height][x % self.width] = state


    def live_a_generation(grid, sim):
    progeny = Grid(grid.height, grid.width)
    item = next(sim)
    while item is not TICK:
    if isinstance(item, Query):
    state = grid[item.y, item.x]
    item = sim.send(state)
    else: # Must be a Transition
    progeny[item.y, item.x] = item.state
    item = next(sim)
    return progeny


    class ColumnPrinter(object):
    def __init__(self):
    self.columns = []

    def append(self, data):
    self.columns.append(data)

    def __str__(self):
    row_count = 1
    for data in self.columns:
    row_count = max(row_count, len(data.splitlines()) + 1)
    rows = [''] * row_count
    for j in range(row_count):
    for i, data in enumerate(self.columns):
    line = data.splitlines()[max(0, j - 1)]
    if j == 0:
    padding = ' ' * (len(line) // 2)
    rows[j] += padding + str(i) + padding
    else:
    rows[j] += line
    if (i + 1) < len(self.columns):
    rows[j] += ' | '
    return '\n'.join(rows)