Minimalistic Game of Life simulator in Python

For scripts to aid with computation or simulation in cellular automata.
Post Reply
User avatar
Gustavo6046
Posts: 647
Joined: December 7th, 2013, 6:26 pm
Location: Brazil.

Minimalistic Game of Life simulator in Python

Post by Gustavo6046 » April 2nd, 2017, 10:40 am

Hello pals, I'm back! I don't remember how far have I gone in this community, but I have been doing a minimalistic, extendable and quick Game of Life simulator, in Python.

Code:

Code: Select all

import re
import os
import time
import sys
import operator

default_rle = """
x = 5, y = -2
24bo11b$22bobo11b$12b2o6b2o12b2o$11bo3bo4b2o12b2o$2o8bo5bo3b2o14b$2o8b
o3bob2o4bobo11b$10bo5bo7bo11b$11bo3bo20b$12b2o!
"""

born = re.compile(r"^b(\d+)")
surv = re.compile(r"^s(\d+)")

neighbors = [
    (-1, -1),
    (-1, 0),
    (-1, 1),
    (0, 1),
    (1, 1),
    (1, 0),
    (1, -1),
    (0, -1),
]

def repeat(i, n):
    return [i[a:a+n] for a in xrange(0, len(i), n)]

def rle_decode(text):
    return re.sub(r'(\d+)([^\d])', lambda m: m.group(2) * int(m.group(1)), text)

def encode(text):
    return re.sub(r'(.)\1*', lambda m: str(len(m.group(0))) + m.group(1), text)

class GolTable(object):
    rle_header = re.compile(r"^x\s*=\s*(\d+),?\s*y\s*=\s*(\d+)")

    def __init__(self, rule="b3/s23", char=[".", "+", "@", "P", "~", "#"]): # LifeHistory-ready charmap.
        self.coords = {}
        self.bounds = [None, None, None, None]  # top, left, bottom, right
        self.max_bounds = self.bounds

        # Display setup
        self.char = char
        self.no_char = char[0]
        self.unknown_char = "?"

        self.rulestring = rule

        if type(rule) is str:
            b = set()
            s = set()

            rset = rule.split("/")

            for a in rset:
                if born.match(a) is not None:
                    for x in a[1:]:
                        b.add(int(x))

                elif surv.match(a) is not None:
                    for x in a[1:]:
                        s.add(int(x))

            self.rules = {
                "born": b,
                "surv": s,
            }

        else:
            self.rules = dict(rules)

    def set_cell(self, x, y, state):
        if state == 0:
            if (x, y) in self.coords:
                del self.coords[(x, y)]
                self.get_bounds()

                return True

            return False

        else:
            if (x, y) in self.coords and self.coords[(x, y)] == state:
                return False

            self.coords[(x, y)] = state

            self.get_bounds()

            return True

    def get_cell(self, x, y):
        if (x, y) in self.coords:
            return self.coords[(x, y)]

        return 0

    def update_bounds(self, top, left, bottom, right):
        self.bounds = [top, left, bottom, right]

    def _bound(self, c):
        x = c[0]
        y = c[1]

        if self.bounds[1] is None or x <= self.bounds[1]:
            self.bounds[1] = x

        if self.bounds[3] is None or x > self.bounds[3]:
            self.bounds[3] = x

        if self.bounds[0] is None or y <= self.bounds[0]:
            self.bounds[0] = y

        if self.bounds[2] is None or y > self.bounds[2]:
            self.bounds[2] = y

    def _max_bound(self, c):
        x = c[0]
        y = c[1]

        if self.max_bounds[1] is None or x <= self.max_bounds[1]:
            self.max_bounds[1] = x

        if self.max_bounds[3] is None or x > self.max_bounds[3]:
            self.max_bounds[3] = x

        if self.max_bounds[0] is None or y <= self.max_bounds[0]:
            self.max_bounds[0] = y

        if self.max_bounds[2] is None or y > self.max_bounds[2]:
            self.max_bounds[2] = y

    def get_bounds(self, bias=0):
        self.bounds = [None, None, None, None]

        for c, s in self.coords.items():
            if s > bias:
                self._bound(c)
                self._max_bound(c)

    def copy(self, other, rules=False):
        other.bounds = self.bounds
        other.coords = self.coords

        if rules:
            other.rules = self.rules

    def step(self, b=None):
        if self.population == 0:
            return False

        if not b:
            b = self.bounds

        self.get_bounds()
        other = GolTable()

        for y in xrange(b[0] - 1, b[2] + 2):
            for x in xrange(b[1] - 1, b[3] + 2):
                self._step_cell(x, y, other)

        other.copy(self)
        del other

        return True

    def neighbors(self, x, y):
        r = 0

        surrounding = [map(operator.add, (x, y), nc) for nc in neighbors]
        assert len(surrounding) == 8

        for s in surrounding:
            r += self.get_cell(*s)

        return r

    def _step_cell(self, x, y, other=None):
        n = self.neighbors(x, y)

        if not other:
            other = self

        if n in self.rules["born"] and self.get_cell(x, y) == 0:
            if type(self.rules["born"]) in (set, list, tuple):
                other.set_cell(x, y, 1)

            else:
                other.set_cell(x, y, self.rules["born"][n])

        elif n in self.rules["surv"] and self.get_cell(x, y) == 1:
            other.set_cell(x, y, 1)

    def to_rle(self):
        out = ""

        for y in xrange(b[0], b[2] + 1):
            for x in xrange(b[1], b[3] + 1):
                out += self.char[self.get_coords[(x, y)]]

            out += "\n"

        return rle_encode(out)

    @classmethod
    def from_rle(cls, rle, rule="b3/s23", char=[".", "+", "@", "P", "~", ";"]):
        inp = rle_decode(rle)

        new = cls(rule, char)

        x = 0
        y = 0

        for c in inp:
            if c == "\n":
                y += 1
                continue

            new.set_cell(x, y, char.index(c))

            x += 1

        return new

    @classmethod
    def from_rle_standard(cls, rle, rule="b3/s23", char=[".", "+", "@", "P", "~", ";"]):
        rle = "\n".join(l for l in rle.splitlines() if not l.startswith("#") and l not in ("", " "))
        root = [0, 0]

        lines = rle.splitlines()
        m = cls.rle_header.match(lines[0])

        if m is not None:
            rle = "\n".join(lines[1:])
            root = [int(x) for x in m.groups()]

        inp = rle_decode(rle)

        new = cls(rule, char)

        x = root[0]
        y = root[1]

        for c in inp:
            if c == "!":
                return new

            if c == "$":
                y += 1
                x = root[0]
                continue

            if c not in ("o, b"):
                continue

            new.set_cell(x, y, (1 if c == "o" else 0))

            if c in ("o", "b"):
                x += 1

        return new

    def display(self, b=None):
        disp = ""

        self.get_bounds()

        if not b:
            b = self.bounds

        for y in xrange(b[0], b[2] + 1):
            for x in xrange(b[1], b[3] + 1):
                if (x, y) in self.coords:
                    try:
                        disp += self.char[self.coords[(x, y)]]

                    except IndexError:
                        disp += self.unknown_char

                else:
                    disp += self.no_char

            disp += "\n"

        return disp

    def _print(self, b=None):
        print self.display(b)

    def population(self, bias=0):
        return len([x for x in self.coords.values() if x > bias])

if __name__ == "__main__": # test
    try:
        t = GolTable.from_rle_standard(open(sys.argv[1]).read())

    except IndexError:
        t = GolTable.from_rle_standard(default_rle)

    print_time = 1
    overhead = 1

    while True:
        overhead += 1
        if overhead % print_time == 0:
            t._print(t.max_bounds)

        try:
            t.step()

        except ValueError:
            print "End of simulation!"

        time.sleep(.001)

        if overhead % print_time == 0:
            os.system("cls")
Instructions:
a) Paste into a file e.g. main.py
b) Make sure you have Python 2.7 (tested-with version) installed! (report if it works with lower versions)
c) Get your command line, `cd` to the folder you put the file with the code and do:

Code: Select all

python <filename>.py <rle filename>.rle
It should run the RLE in the command-line, in a Golly-like environment.

You can also import the file and use it as a Game of Life library! :D

I have recorded a short video, available here.
*yawn* What a nothing-to-do day! Let's be the only person in the world to do CGOL during boring times. :)

User avatar
Billabob
Posts: 158
Joined: April 2nd, 2015, 5:28 pm

Re: Minimalistic Game of Life simulator in Python

Post by Billabob » September 11th, 2018, 6:50 pm

Hunting wrote:Wow!
Please don’t post single-word replies on threads that have been dead for over a year. It needlessly clutters up the forums.
▄▀
▀▀▀

User avatar
Gustavo6046
Posts: 647
Joined: December 7th, 2013, 6:26 pm
Location: Brazil.

Re: Minimalistic Game of Life simulator in Python

Post by Gustavo6046 » September 11th, 2018, 7:21 pm

I checked my Gmail, and surprise surprise! I believe I'll just change my avatar and other things. I want to look decent in here.

On topic, I believe I'll remake the CGOL player.
*yawn* What a nothing-to-do day! Let's be the only person in the world to do CGOL during boring times. :)

Hunting
Posts: 4395
Joined: September 11th, 2017, 2:54 am

Re: Minimalistic Game of Life simulator in Python

Post by Hunting » September 11th, 2018, 9:00 pm

Billabob wrote:
Hunting wrote:Wow!
Please don’t post single-word replies on threads that have been dead for over a year. It needlessly clutters up the forums.
Okay... I just want to check if Gustavo is alive or something. I'll delete that.

Hunting
Posts: 4395
Joined: September 11th, 2017, 2:54 am

Re: Minimalistic Game of Life simulator in Python

Post by Hunting » September 11th, 2018, 9:08 pm

Gustavo6046 wrote:I checked my Gmail, and surprise surprise! I believe I'll just change my avatar and other things. I want to look decent in here.

On topic, I believe I'll remake the CGOL player.
Hi, Gustavo! There's a bunch of MINIMALISTIC GoL simulator (not in Python) on the Internet. I'll show you one: GoL simulator in JS1K. (That simulator didn't use your RLE copy/paste init-state input method. In that simulator, you can see a big grid just like Golly, but without many options.)
http://js1k.com/2012-love/demo/1111
(Your script looks awesome, but my Python level is like a beginner.)
By the way, I will PM(Private Message) you about GoL tech.
(You are in South of Brazil?)

User avatar
77topaz
Posts: 1496
Joined: January 12th, 2018, 9:19 pm

Re: Minimalistic Game of Life simulator in Python

Post by 77topaz » September 11th, 2018, 9:22 pm

Hunting wrote:Okay... I just want to check if Gustavo is alive or something. I'll delete that.
Replacing the contents of the post with the word "DELETED" doesn't help anyone. Just don't do this again in the future...

Hunting
Posts: 4395
Joined: September 11th, 2017, 2:54 am

Re: Minimalistic Game of Life simulator in Python

Post by Hunting » September 11th, 2018, 9:40 pm

77topaz wrote:
Hunting wrote:Okay... I just want to check if Gustavo is alive or something. I'll delete that.
Replacing the contents of the post with the word "DELETED" doesn't help anyone. Just don't do this again in the future...
Okay.

User avatar
Gustavo6046
Posts: 647
Joined: December 7th, 2013, 6:26 pm
Location: Brazil.

Re: Minimalistic Game of Life simulator in Python

Post by Gustavo6046 » September 14th, 2018, 9:16 pm

(a) I've gathered a LOT of Python knowledge since then, and I may guarantee you, that is bulky and inefficient. I can do a much faster method (considering I don't usually use NumPy). I will create a Golly-independent Python script (probably with wxWidgets) that will search for combinations of input patterns Aₓ that generate the desired result pattern B. (with multiple configurations for position ranges, etc). I'm already working on it.

This will probably use a brute force method, won't be as fast as an actual CGoL algorithm (especially, it will be MUCH slower than HashLife), one of the reasons being it will be in Python.

(b) Yes, I live in Brazil.
*yawn* What a nothing-to-do day! Let's be the only person in the world to do CGOL during boring times. :)

Hunting
Posts: 4395
Joined: September 11th, 2017, 2:54 am

Re: Minimalistic Game of Life simulator in Python

Post by Hunting » September 20th, 2018, 11:28 am

Gustavo6046 wrote:(a) I've gathered a LOT of Python knowledge since then, and I may guarantee you, that is bulky and inefficient. I can do a much faster method (considering I don't usually use NumPy). I will create a Golly-independent Python script (probably with wxWidgets) that will search for combinations of input patterns Aₓ that generate the desired result pattern B. (with multiple configurations for position ranges, etc). I'm already working on it.

This will probably use a brute force method, won't be as fast as an actual CGoL algorithm (especially, it will be MUCH slower than HashLife), one of the reasons being it will be in Python.

(b) Yes, I live in Brazil.
(a) Wow.
(b) you are the first person who lived in brazil ive everseen.

Hunting
Posts: 4395
Joined: September 11th, 2017, 2:54 am

Re: Minimalistic Game of Life simulator in Python

Post by Hunting » September 20th, 2018, 11:48 am

(c) check your private message, then.

User avatar
Entity Valkyrie
Posts: 247
Joined: November 30th, 2017, 3:30 am

Re: Minimalistic Game of Life simulator in Python

Post by Entity Valkyrie » December 1st, 2018, 2:17 am

By a different entity, Yizientity:

https://repl.it/@YuzhiEntity/Behaulken

Hunting
Posts: 4395
Joined: September 11th, 2017, 2:54 am

Re: Minimalistic Game of Life simulator in Python

Post by Hunting » February 4th, 2020, 10:08 am

Gustavo6046 wrote:
September 11th, 2018, 7:21 pm
I checked my Gmail, and surprise surprise! I believe I'll just change my avatar and other things. I want to look decent in here.

On topic, I believe I'll remake the CGOL player.
I just want to bump this thread, to remind Gustavo of this thread, this community, and this everything.

Sorry everyone.

I was like Gustavo when I joined this community.

Post Reply