Conway's Game of Life

Code-Stücke können hier veröffentlicht werden.
Antworten
BlackJack

Eine aktualisierte Fassung von Conway's Game of Life die ich in einem anderen Thread gepostet hatte.

Diesmal ist das Feld wirklich "unendlich" gross und man kann im Konstruktor einen Ausschnitt zum Anzeigen, sowie die Regeln für das Überleben bzw. die Geburt einer Zelle, übergeben.

Code: Alles auswählen

#!/usr/bin/env python
"""Conway's *Game Of Life* in Python.

Conway's *Game Of Life* is a cellular automaton.  There is a field with cells
which are either alive or dead.  In each step of the "game" cells die or are
born, depending on the number of living cells in their neighbourhood.  The
rules of the classic game are:

- a living cell stays alive if there are two or three living neighbours,
- and a dead cell is resurrected if there are exactly three living neighbours.

This can be expressed as the string ``23/3`` where the digits before the slash
tell how many living neighbours are needed for a cell to stay alive and the
digits after the slash how many cells are needed to spawn a new cell.  There
are other rules that produce interesting patterns too.
"""
from __future__ import division
import random
import time

__author__ = "Marc 'BlackJack' Rintsch"
__version__ = '0.2.0'
__date__ = '$Date: 2006-04-17 16:59:11 +0200 (Mon, 17 Apr 2006) $'
__revision__ = '$Rev: 843 $'


def bounding_box(points):
    """Return the minimal rectangular bounding box that contains all given
    `points`.
    
    All coordinates are "screen coordinates", i.e. the higher a *y* value the
    lower is the point "on screen".
    
    If `points` contains no points at all the value ``(0, 0, 0, 0)`` is
    returned.
    
    points : iterable of (number, number)
        The (x, y) coordinates.
    
    Return a tuple with four numbers that represent the bounding box.  The
    first two numbers describe the upper left corner and the last two numbers
    describe the lower right corner.
    """
    points = iter(points)
    
    try:
        left, upper = right, lower = points.next()
    except StopIteration:
        left, upper = right, lower = (0, 0)
    
    for x, y in points:
        if x < left:
            left = x
        elif x > right:
            right = x
        if y < upper:
            upper = y
        elif y > lower:
            lower = y
    
    assert upper <= lower and left <= right
    
    return (left, upper, right, lower)


def contains((x_1, y_1, x_2, y_2), (x, y)):
    """Check if the given box contains the point."""
    return x_1 <= x <= x_2 and y_1 <= y <= y_2


class GameOfLife(object):
    """Conway's *Game of Life*
    
    NEIGHBOUR_POSITIONS : [(int, int)]
        Relative coordinates of eight neighbour fields.
    
    cells : set((int, int))
        Coordinates of the living cells.
    viewport : (int, int, int, int)
        Bounding box of the viewport.
    clock : int
        Current step number.
    rule : str
        The rule for the game.
    """
    NEIGHBOUR_POSITIONS = [(x, y) for x in xrange(-1, 2)
                                  for y in xrange(-1, 2)
                                  if not x == y == 0]
    
    def __init__(self, cells=None, viewport=None, rule='23/3'):
        self.cells = set(cells)
        self.viewport = viewport or bounding_box(cells)
        self.clock = 0
        
        self._life_rule = None
        self._resurrect_rule = None
        self.rule = rule
    
    def __len__(self):
        """Return the number of living cells."""
        return len(self.cells)
    
    def __str__(self):
        """Return a map of the viewport."""
        width = self.viewport[2] - self.viewport[0]
        height = self.viewport[3] - self.viewport[1]
        assert width >= 0 and height >= 0
        
        worldmap = [['.'] * (width + 1) for dummy in xrange(height + 1)]
        for x, y in self.iter_cells_in_viewport():
            worldmap[y][x] = '#'
        return ('Clock: %d  Cells: %d\n' % (self.clock, len(self))
                + '\n'.join([''.join(row) for row in worldmap]))

    def _get_rule(self):
        return (''.join(map(str, self._life_rule))
                + '/'
                + ''.join(map(str, self._resurrect_rule)))
    
    def _set_rule(self, rule):
        try:
            life, resurrect = rule.split('/')
            self._life_rule = map(int, life)
            self._resurrect_rule = map(int, resurrect)
        except ValueError:
            raise ValueError('invalid rule %r' % rule)
    
    rule = property(_get_rule, _set_rule)
    
    def _neighbour_fields(self, (x, y)):
        """Return iterator over the coordinates of the neighbour fields of
        the field (`x`, `y`).
        """
        return ((x + delta_x, y + delta_y)
                for (delta_x, delta_y) in self.NEIGHBOUR_POSITIONS)
    
    def _fields_of_interest(self):
        """Determine the fields that can change.
        
        Return an iterable with the coordinates of living cells and their
        neighbour fields.
        """
        fields = set(self.cells)
        for cell in self.cells:
            fields.update(self._neighbour_fields(cell))
        return fields

    def random_fill(self, probability=0.5, box=None):
        """Spawn cells randomly.
        
        Each cell within `box` has a chance of `probability` to become alive.
        If no `box` is given, this method acts on `self.viewport`.
        """
        if box is None:
            box = self.viewport
        for x in xrange(box[0], box[2]):
            for y in xrange(box[1], box[3]):
                print x, y, random.random()
                if random.random() <= probability:
                    self.cells.add((x, y))
    
    def iter_cells_in_viewport(self):
        """Iterate over living cells within viewport."""
        return (cell for cell in self.cells if contains(self.viewport, cell))
    
    def step(self):
        """Calculate the next step."""
        new_cells = set()
        
        for field in self._fields_of_interest():
            alive_neighbours = sum((int(neighbour_field in self.cells)
                                    for neighbour_field
                                    in self._neighbour_fields(field)))
            if field in self.cells:
                if alive_neighbours in self._life_rule:
                    new_cells.add(field)
            else:
                if alive_neighbours in self._resurrect_rule:
                    new_cells.add(field)
        
        self.cells = new_cells
        self.clock += 1
    
    def run(self, callback=lambda game: None, steps=1000000):
        """Run the game the given `steps` and optionally takes a `callback`
        that is called after each step with the game (`self`) as argument.
        """
        while len(self) and self.clock < steps:
            self.step()
            callback(self)


def main():
    """Main function."""
    def display(obj, sleep_time=0.25):
        """Clear the screen, display `obj` as string and wait `sleep_time`
        seconds.
        
        Uses an ANSI escape sequence to clear the screen.
        """
        print '\033[H%s' % obj  # Cursor home and `obj` as string.
        time.sleep(sleep_time)

    # 
    # Sample game data.
    # 
    game = GameOfLife(((5, 3), (4, 4), (3, 5), (4, 6),
                       (5, 7), (6, 6), (7, 5), (6, 4)),
                      (0, 0, 78, 22), '23/3')
    game.random_fill(0.5, (30, 5, 50, 15))
    
    print '\033[2J'     # Clear screen.
    try:
        game.run(display)
    except KeyboardInterrupt:
        print 'Bye...'


if __name__ == '__main__':
    main()
BlackJack

Nach 8 Jahren mal eine Aktualisierung, nach dem ich Stop Writing Classes sah, hatte ich das starke Bedürfnis ein Tupel durch eine eigene Klasse zu ersetzen. :-)

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
"""Conway's *Game Of Life* in Python.

Conway's *Game Of Life* is a cellular automaton.  There is a field with cells
which are either alive or dead.  In each step of the "game" cells die or are
born, depending on the number of living cells in their neighbourhood.  The
rules of the classic game are:

- a living cell stays alive if there are two or three living neighbours,
- and a dead cell is resurrected if there are exactly three living neighbours.

This can be expressed as the string ``23/3`` where the digits before the slash
tell how many living neighbours are needed for a cell to stay alive and the
digits after the slash how many cells are needed to spawn a new cell.  There
are other rules that produce interesting patterns too.
"""
from __future__ import division, print_function
import random
import time
from future_builtins import map
from itertools import chain

__author__ = "Marc 'BlackJack' Rintsch"
__version__ = '0.3.0'
__date__ = '2014-06-11'


class BoundingBox(object):
    """A bounding box which can be created from a bunch of coordinates.

    Coordinates can be added to get an extended bounding box and coordinates
    can be tested if they are inside the box.

    Any BoundingBox contains at least one coordinate, so the minimum width
    and heigt is 1.

    x_1, y_1 : int, int
        Upper left corner.
    x_2, y_2 : int, int
        Lower right corner.
    width : int
        The width of the box.
    height : int
        The height of the box.
    """
    def __init__(self, x_1=0, y_1=0, x_2=None, y_2=None):
        """Create a box.

        If no values for the lower right corner are given, the coordinates
        of the upper right corner are used.
        """
        self.x_1 = x_1
        self.y_1 = y_1
        self.x_2 = x_1 if x_2 is None else x_2
        self.y_2 = y_1 if y_2 is None else y_2

    def __add__(self, (x, y)):
        """Add a coordinate by returning a bounding box that is extended to
        contain the given coordinate.
        """
        return BoundingBox(
            min(self.x_1, x),
            min(self.y_1, y),
            max(self.x_2, x),
            max(self.y_2, y)
        )

    def __contains__(self, (x, y)):
        """Test if the box contains the given coordinate."""
        return self.x_1 <= x <= self.x_2 and self.y_1 <= y <= self.y_2

    @property
    def width(self):
        return self.x_2 - self.x_1 + 1

    @property
    def height(self):
        return self.y_2 - self.y_1 + 1
    
    @classmethod
    def from_points(cls, points):
        """Create a bounding box from given coordinates.

        If there are no coordinates in `points` the bounding box that contains
        just the (0, 0) point is returned.

        points : iterable of (int, int)
        """
        points = iter(points)
        try:
            x, y = next(points)
        except StopIteration:
            x, y = (0, 0)
        return sum(points, cls(x, y))


class GameOfLife(object):
    """Conway's *Game of Life*
    
    NEIGHBOUR_POSITIONS : [(int, int)]
        Relative coordinates of eight neighbour fields.
    
    cells : set((int, int))
        Coordinates of the living cells.
    viewport : BoundingBox
        Bounding box of the viewport.
    clock : int
        Current step number.
    rule : str
        The rule for the game.
    """
    NEIGHBOUR_POSITIONS = [
        (x, y) for x in xrange(-1, 2) for y in xrange(-1, 2) if not x == y == 0
    ]
    
    def __init__(self, cells=None, viewport=None, rule='23/3'):
        self.cells = set(cells)
        self.viewport = (
            BoundingBox.from_points(cells) if viewport is None else viewport
        )
        self.clock = 0
        
        self._life_rule = None
        self._resurrect_rule = None
        self.rule = rule
    
    def __len__(self):
        """Return the number of living cells."""
        return len(self.cells)
    
    def __str__(self):
        """Return a map of the viewport."""
        worldmap = [
            ['.'] * self.viewport.width for _ in xrange(self.viewport.height)
        ]
        for x, y in self.iter_cells_in_viewport():
            worldmap[y][x] = '#'
        return 'Rule: {0}  Clock: {1:d}  Cells: {2:d} \n{3}'.format(
            self.rule,
            self.clock,
            len(self),
            '\n'.join(''.join(row) for row in worldmap)
        )

    @property
    def rule(self):
        return '{0}/{1}'.format(
            ''.join(map(str, self._life_rule)),
            ''.join(map(str, self._resurrect_rule))
        )
    
    @rule.setter
    def rule(self, rule):
        life, slash, resurrect = rule.partition('/')
        if not slash:
            raise ValueError('invalid rule %r' % rule)
        self._life_rule = list(map(int, life))
        self._resurrect_rule = list(map(int, resurrect))
    
    def _neighbour_fields(self, (x, y)):
        """Return iterator over the coordinates of the neighbour fields of
        the field (`x`, `y`).
        """
        return (
            (x + delta_x, y + delta_y)
            for (delta_x, delta_y) in self.NEIGHBOUR_POSITIONS
        )
    
    def _fields_of_interest(self):
        """Determine the fields that can change.
        
        Return an iterable with the coordinates of living cells and their
        neighbour fields.
        """
        return set(
            chain(
                self.cells,
                chain.from_iterable(map(self._neighbour_fields, self.cells))
            )
        )

    def random_fill(self, probability=0.5, box=None):
        """Spawn cells randomly.
        
        Each cell within `box` has a chance of `probability` to become alive.
        If no `box` is given, this method acts on `self.viewport`.
        """
        if box is None:
            box = self.viewport
        for x in xrange(box.x_1, box.x_2):
            for y in xrange(box.y_1, box.y_2):
                if random.random() <= probability:
                    self.cells.add((x, y))
    
    def iter_cells_in_viewport(self):
        """Iterate over living cells within viewport."""
        return (cell for cell in self.cells if cell in self.viewport)
    
    def step(self):
        """Calculate the next step."""
        new_cells = set()
        
        for field in self._fields_of_interest():
            alive_neighbours = sum(
                int(neighbour_field in self.cells)
                for neighbour_field in self._neighbour_fields(field)
            )
            if field in self.cells:
                if alive_neighbours in self._life_rule:
                    new_cells.add(field)
            else:
                if alive_neighbours in self._resurrect_rule:
                    new_cells.add(field)
        
        self.cells = new_cells
        self.clock += 1
    
    def run(self, callback=lambda game: None, steps=None):
        """Run the game the given `steps` and optionally takes a `callback`
        that is called after each step with the game (`self`) as argument.
        """
        while self and (steps is None or self.clock < steps):
            self.step()
            callback(self)


def main():
    """Main function."""

    def display(obj, delay=0.25):
        """Clear the screen, display `obj` as string and wait `delay`
        seconds.
        
        Uses an ANSI escape sequence to move the cursor to the top left
        of the screen.
        """
        print('\033[H', obj, sep='')  # Cursor home and `obj` as string.
        time.sleep(delay)
    # 
    # Sample game data: a ”pulsar” in the upper left and some random cells
    # in the middle.
    # 
    game = GameOfLife(
        ((5, 3), (4, 4), (3, 5), (4, 6), (5, 7), (6, 6), (7, 5), (6, 4)),
        BoundingBox(0, 0, 78, 22),
        '23/3'
    )
    game.random_fill(0.5, BoundingBox(30, 5, 50, 15))
    
    print('\033[2J')  # Clear screen.
    try:
        game.run(display)
    except KeyboardInterrupt:
        print('Bye...')


if __name__ == '__main__':
    main()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Das war noch drei Monate vor meiner Anmeldung im Forum, sonst hätte ich natürlich schon längst etwas gesagt :)
Das Leben ist wie ein Tennisball.
Antworten